@mrclrchtr/supi-code-intelligence 5.0.0 → 6.0.1

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 (151) hide show
  1. package/README.md +4 -2
  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-core/README.md +2 -0
  5. package/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  6. package/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
  7. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -3
  8. package/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
  10. package/node_modules/@mrclrchtr/supi-lsp/README.md +36 -3
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -3
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
  19. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  20. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +12 -1
  21. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +67 -28
  22. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +63 -6
  23. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +46 -5
  24. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +1 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +214 -0
  26. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +278 -79
  27. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +29 -8
  28. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +101 -19
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +19 -1
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +49 -3
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +73 -15
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +1 -1
  33. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +2 -2
  34. package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +5 -5
  35. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +3 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-extends.ts +68 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-path.ts +8 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +176 -24
  39. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +4 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +46 -10
  41. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -3
  42. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +65 -17
  43. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +143 -21
  44. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +2 -0
  45. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +43 -1
  46. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +31 -0
  47. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +2 -0
  48. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  49. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  50. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  51. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  52. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
  53. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -3
  54. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
  55. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +4 -4
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +1 -1
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +4 -4
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  62. package/package.json +5 -5
  63. package/src/analysis/health/diagnostics.ts +58 -126
  64. package/src/analysis/health/file-scope.ts +184 -0
  65. package/src/analysis/health/recovery.ts +8 -1
  66. package/src/analysis/readiness.ts +4 -0
  67. package/src/analysis/search/ast-scan.ts +4 -1
  68. package/src/analysis/search/pattern-analysis.ts +10 -3
  69. package/src/analysis/search/pattern.ts +6 -1
  70. package/src/config.ts +35 -1
  71. package/src/extension.ts +57 -14
  72. package/src/overview/overview-data.ts +9 -2
  73. package/src/overview/overview.ts +49 -30
  74. package/src/overview/types.ts +2 -0
  75. package/src/session/find-types.ts +2 -2
  76. package/src/session/find-workflow.ts +3 -2
  77. package/src/session/health-refresh.ts +76 -49
  78. package/src/session/health-types.ts +7 -1
  79. package/src/session/health-workflow.ts +35 -18
  80. package/src/session/input/workflows.ts +2 -2
  81. package/src/session/inspect-workflow.ts +2 -1
  82. package/src/session/orientation/collect.ts +2 -2
  83. package/src/session/orientation-types.ts +2 -0
  84. package/src/session/orientation-workflow.ts +8 -2
  85. package/src/session/refactor-workflow.ts +2 -1
  86. package/src/session/session.ts +25 -1
  87. package/src/substrate/lsp/maintenance.ts +105 -23
  88. package/src/substrate/lsp/recovery.ts +11 -5
  89. package/src/tool/code_find/execute.ts +23 -0
  90. package/src/tool/code_find/guidance.ts +8 -0
  91. package/src/tool/{find → code_find}/render.ts +1 -1
  92. package/src/tool/{result/find.ts → code_find/result.ts} +36 -11
  93. package/src/tool/code_find/spec.ts +37 -0
  94. package/src/tool/code_graph/execute.ts +24 -0
  95. package/src/tool/code_graph/guidance.ts +6 -0
  96. package/src/tool/{graph → code_graph}/markdown.ts +1 -1
  97. package/src/tool/{result/graph.ts → code_graph/result.ts} +69 -4
  98. package/src/tool/code_graph/spec.ts +39 -0
  99. package/src/tool/{health → code_health}/execute.ts +2 -18
  100. package/src/tool/code_health/guidance.ts +8 -0
  101. package/src/tool/{health → code_health}/markdown.ts +68 -18
  102. package/src/tool/{result/health.ts → code_health/result.ts} +29 -4
  103. package/src/tool/code_health/spec.ts +41 -0
  104. package/src/tool/code_inspect/execute.ts +20 -0
  105. package/src/tool/code_inspect/guidance.ts +6 -0
  106. package/src/tool/{inspect → code_inspect}/markdown.ts +1 -1
  107. package/src/tool/{result/inspect.ts → code_inspect/result.ts} +32 -3
  108. package/src/tool/code_inspect/spec.ts +25 -0
  109. package/src/tool/code_orientation/execute.ts +25 -0
  110. package/src/tool/code_orientation/guidance.ts +9 -0
  111. package/src/tool/{orientation → code_orientation}/markdown.ts +1 -1
  112. package/src/tool/{result/orientation.ts → code_orientation/result.ts} +67 -3
  113. package/src/tool/code_orientation/spec.ts +25 -0
  114. package/src/tool/code_refactor_apply/execute.ts +17 -0
  115. package/src/tool/code_refactor_apply/guidance.ts +5 -0
  116. package/src/tool/{refactor-apply/execute.ts → code_refactor_apply/result.ts} +5 -11
  117. package/src/tool/code_refactor_apply/spec.ts +26 -0
  118. package/src/tool/code_refactor_plan/execute.ts +27 -0
  119. package/src/tool/code_refactor_plan/guidance.ts +8 -0
  120. package/src/tool/{refactor-plan/execute.ts → code_refactor_plan/result.ts} +9 -22
  121. package/src/tool/code_refactor_plan/spec.ts +25 -0
  122. package/src/tool/code_resolve/execute.ts +19 -0
  123. package/src/tool/code_resolve/guidance.ts +8 -0
  124. package/src/tool/{resolve → code_resolve}/markdown.ts +1 -1
  125. package/src/tool/{result/resolve.ts → code_resolve/result.ts} +37 -24
  126. package/src/tool/code_resolve/spec.ts +25 -0
  127. package/src/tool/guidance.ts +71 -52
  128. package/src/tool/infra/truncate.ts +40 -0
  129. package/src/tool/{refactor-plan/markdown.ts → refactor-markdown.ts} +4 -7
  130. package/src/tool/register.ts +9 -97
  131. package/src/tool/result/refactor.ts +1 -4
  132. package/src/tool/schemas.ts +26 -157
  133. package/src/tool/specs.ts +40 -126
  134. package/src/tool/find/execute.ts +0 -43
  135. package/src/tool/graph/execute.ts +0 -82
  136. package/src/tool/inspect/execute.ts +0 -40
  137. package/src/tool/orientation/execute.ts +0 -80
  138. package/src/tool/resolve/execute.ts +0 -53
  139. /package/src/tool/{find → code_find}/ast-kinds.ts +0 -0
  140. /package/src/tool/{find → code_find}/markdown.ts +0 -0
  141. /package/src/tool/{find → code_find}/modes.ts +0 -0
  142. /package/src/tool/{find → code_find}/tui.ts +0 -0
  143. /package/src/tool/{graph → code_graph}/tui.ts +0 -0
  144. /package/src/tool/{health → code_health}/refresh-status.ts +0 -0
  145. /package/src/tool/{health → code_health}/semantic-state.ts +0 -0
  146. /package/src/tool/{health → code_health}/tui.ts +0 -0
  147. /package/src/tool/{inspect → code_inspect}/tui.ts +0 -0
  148. /package/src/tool/{orientation → code_orientation}/tui.ts +0 -0
  149. /package/src/tool/{refactor-apply → code_refactor_apply}/tui.ts +0 -0
  150. /package/src/tool/{refactor-plan → code_refactor_plan}/tui.ts +0 -0
  151. /package/src/tool/{resolve → code_resolve}/tui.ts +0 -0
@@ -4,6 +4,7 @@ import type { CapabilityState } from "@mrclrchtr/supi-code-runtime/api";
4
4
  import { isCodeRequestInterruption } from "@mrclrchtr/supi-code-runtime/api";
5
5
  import type {
6
6
  LspRuntimeController,
7
+ WorkspaceDiagnosticReport,
7
8
  WorkspaceLspRuntime,
8
9
  WorkspaceLspRuntimeState,
9
10
  } from "@mrclrchtr/supi-lsp/api";
@@ -18,6 +19,7 @@ import {
18
19
  } from "../analysis/health/diagnostics.ts";
19
20
  import { describeStructuralState } from "../analysis/health/recovery.ts";
20
21
  import { collectServers } from "../analysis/health/signals.ts";
22
+ import { SEMANTIC_READINESS_TIMEOUT_REASON } from "../analysis/readiness.ts";
21
23
  import { resolveScope } from "../analysis/search/paths.ts";
22
24
  import type { CapabilityAdapter } from "./capability-adapter.ts";
23
25
  import { collectHealthRefreshAttempt } from "./health-refresh.ts";
@@ -72,7 +74,7 @@ export async function runHealthWorkflow(
72
74
  const serverInventoryAvailable = lspState.kind === "ready" || lspState.kind === "disabled";
73
75
 
74
76
  const diagnosticsScope = diagnosticScope(scopeFilter);
75
- const refresh = await collectRefreshState({
77
+ const refreshCollection = await collectRefreshState({
76
78
  refreshRequested: request.refresh === true,
77
79
  diagnosticsRequested: included.includes("diagnostics"),
78
80
  runtime,
@@ -102,10 +104,12 @@ export async function runHealthWorkflow(
102
104
  detailed: level === "detailed",
103
105
  requestControl: control,
104
106
  refreshEvidence:
105
- (refresh.kind === "completed" || refresh.kind === "failed") &&
106
- refresh.operationScope === "workspace-runtime"
107
- ? refresh.diagnosticEvidence
107
+ (refreshCollection.attempt.kind === "completed" ||
108
+ refreshCollection.attempt.kind === "failed") &&
109
+ refreshCollection.attempt.operationScope === "workspace-runtime"
110
+ ? refreshCollection.attempt.diagnosticEvidence
108
111
  : undefined,
112
+ refreshReport: refreshCollection.diagnosticReport,
109
113
  });
110
114
  // A cancellation that landed during diagnostic resolution must not produce
111
115
  // a completed health result the caller no longer awaits.
@@ -121,7 +125,7 @@ export async function runHealthWorkflow(
121
125
  structuralStatus: describeStructuralState(capabilityStates.structural),
122
126
  diagnostics,
123
127
  servers,
124
- refresh,
128
+ refresh: refreshCollection.attempt,
125
129
  level,
126
130
  capabilityWarnings: capabilityWarnings?.hasWarnings ? capabilityWarnings : undefined,
127
131
  };
@@ -154,7 +158,7 @@ async function establishSemanticHealthState(
154
158
  );
155
159
  if (readiness.kind === "ready") return { kind: "ready" };
156
160
  if (readiness.kind === "timeout") {
157
- return { kind: "pending", reason: "File semantic readiness timed out" };
161
+ return { kind: "pending", reason: SEMANTIC_READINESS_TIMEOUT_REASON };
158
162
  }
159
163
  return { kind: "unavailable", reason: readiness.reason };
160
164
  }
@@ -213,6 +217,11 @@ function collectCapabilityWarnings(
213
217
  return report.hasWarnings ? report : undefined;
214
218
  }
215
219
 
220
+ interface HealthRefreshStateCollection {
221
+ readonly attempt: HealthRefreshState;
222
+ readonly diagnosticReport?: WorkspaceDiagnosticReport;
223
+ }
224
+
216
225
  interface RefreshStateOptions {
217
226
  readonly refreshRequested: boolean;
218
227
  readonly diagnosticsRequested: boolean;
@@ -224,27 +233,35 @@ interface RefreshStateOptions {
224
233
  }
225
234
 
226
235
  /** Run the requested workspace-runtime refresh and retain only facts the runtime establishes. */
227
- async function collectRefreshState(options: RefreshStateOptions): Promise<HealthRefreshState> {
236
+ async function collectRefreshState(
237
+ options: RefreshStateOptions,
238
+ ): Promise<HealthRefreshStateCollection> {
228
239
  const { deps, diagnosticsRequested, diagnosticsScope, lspState, runtime } = options;
229
240
  if (!diagnosticsRequested) {
230
241
  return {
231
- kind: "not-requested",
232
- reason: "Diagnostics were not requested.",
233
- lastAttempt: deps.lastRefreshAttempt,
242
+ attempt: {
243
+ kind: "not-requested",
244
+ reason: "Diagnostics were not requested.",
245
+ lastAttempt: deps.lastRefreshAttempt,
246
+ },
234
247
  };
235
248
  }
236
249
  if (!options.refreshRequested) {
237
250
  return {
238
- kind: "not-requested",
239
- reason: "Refresh was not requested.",
240
- lastAttempt: deps.lastRefreshAttempt,
251
+ attempt: {
252
+ kind: "not-requested",
253
+ reason: "Refresh was not requested.",
254
+ lastAttempt: deps.lastRefreshAttempt,
255
+ },
241
256
  };
242
257
  }
243
258
  if (!runtime) {
244
259
  return {
245
- kind: "not-attempted",
246
- reason: refreshUnavailableReason(lspState),
247
- lastAttempt: deps.lastRefreshAttempt,
260
+ attempt: {
261
+ kind: "not-attempted",
262
+ reason: refreshUnavailableReason(lspState),
263
+ lastAttempt: deps.lastRefreshAttempt,
264
+ },
248
265
  };
249
266
  }
250
267
 
@@ -270,7 +287,7 @@ async function collectRefreshState(options: RefreshStateOptions): Promise<Health
270
287
  message: "Refreshing diagnostics and recovery state",
271
288
  }),
272
289
  });
273
- deps.trackRefreshAttempt(attempt);
290
+ deps.trackRefreshAttempt(attempt.attempt);
274
291
  return attempt;
275
292
  } catch (error) {
276
293
  // Cancellation must propagate; a cancelled caller no longer awaits a
@@ -285,7 +302,7 @@ async function collectRefreshState(options: RefreshStateOptions): Promise<Health
285
302
  reason: errorMessage(error),
286
303
  };
287
304
  deps.trackRefreshAttempt(attempt);
288
- return attempt;
305
+ return { attempt };
289
306
  }
290
307
  }
291
308
 
@@ -1,7 +1,7 @@
1
1
  /** Runtime parsers for inspection, Orientation, graph, and find workflows. */
2
2
 
3
- import { type CodeFindAstKind, isCodeFindAstKind } from "../../tool/find/ast-kinds.ts";
4
- import { CODE_FIND_MODES } from "../../tool/find/modes.ts";
3
+ import { type CodeFindAstKind, isCodeFindAstKind } from "../../tool/code_find/ast-kinds.ts";
4
+ import { CODE_FIND_MODES } from "../../tool/code_find/modes.ts";
5
5
  import type { FindMode, FindWorkflowInput } from "../find-types.ts";
6
6
  import type { GraphWorkflowInput, RequestedGraphRelation } from "../graph-types.ts";
7
7
  import type { InspectWorkflowInput } from "../inspect-types.ts";
@@ -2,6 +2,7 @@
2
2
 
3
3
  import type { WorkspaceLspRuntimeState } from "@mrclrchtr/supi-lsp/api";
4
4
  import { withSemanticRequestControl } from "../analysis/provider.ts";
5
+ import { SEMANTIC_READINESS_TIMEOUT_REASON } from "../analysis/readiness.ts";
5
6
  import type { CapabilityAdapter, ReadinessOutcome } from "./capability-adapter.ts";
6
7
  import { parseInspectWorkflowInput } from "./input/workflows.ts";
7
8
  import { collectInspectSections } from "./inspect/collect.ts";
@@ -155,7 +156,7 @@ function semanticReadinessReason(readiness: ReadinessOutcome): string {
155
156
  case "ready":
156
157
  return "No semantic provider is active for this file.";
157
158
  case "timeout":
158
- return "Semantic readiness timed out.";
159
+ return SEMANTIC_READINESS_TIMEOUT_REASON;
159
160
  case "unavailable":
160
161
  return readiness.reason;
161
162
  }
@@ -183,7 +183,7 @@ async function buildEnrichedDefsSection(
183
183
  hasStructuralEvidence,
184
184
  hasSemanticEvidence: contextHasSemanticEvidence,
185
185
  status: contextHasSemanticEvidence ? "partial" : "unavailable",
186
- reason: "Definition targets require a live language server.",
186
+ reason: deps.semanticReadinessReason ?? "Definition targets require a live language server.",
187
187
  evidenceLists: [],
188
188
  };
189
189
  }
@@ -292,7 +292,7 @@ async function buildDiagnosticsSection(
292
292
  },
293
293
  ],
294
294
  false,
295
- "Diagnostics require a live language server.",
295
+ deps.semanticReadinessReason ?? "Diagnostics require a live language server.",
296
296
  );
297
297
  }
298
298
 
@@ -37,6 +37,8 @@ export interface OrientationDeps {
37
37
  readonly provider: CodeProvider | null;
38
38
  readonly cwd: string;
39
39
  readonly lspRuntime: WorkspaceLspRuntimeState;
40
+ /** Typed reason to surface when target semantic readiness timed out. */
41
+ readonly semanticReadinessReason?: string;
40
42
  readonly requestControl?: CodeRequestControl;
41
43
  }
42
44
 
@@ -9,6 +9,7 @@ import {
9
9
  findInstructionFilesForDirectory,
10
10
  } from "../analysis/instruction-files.ts";
11
11
  import { createStructuralCodeProvider, withSemanticRequestControl } from "../analysis/provider.ts";
12
+ import { SEMANTIC_READINESS_TIMEOUT_REASON } from "../analysis/readiness.ts";
12
13
  import { normalizePath } from "../analysis/search/paths.ts";
13
14
  import { loadCodeIntelligenceConfig } from "../config.ts";
14
15
  import type { CapabilityAdapter } from "./capability-adapter.ts";
@@ -33,6 +34,8 @@ export interface OrientationWorkflowDeps extends TargetWorkflowDeps {
33
34
  readonly markInstructionDirsSurfaced: (directories: string[]) => void;
34
35
  /** Whether the project is trusted — controls instruction-file discovery. */
35
36
  readonly projectTrusted: boolean;
37
+ /** Global config home for hermetic config reads; undefined uses os.homedir(). */
38
+ readonly homeDir?: string;
36
39
  }
37
40
 
38
41
  /** Resolve the Orientation focus and collect immutable facts for presentation adapters. */
@@ -146,11 +149,13 @@ async function orientTarget(options: {
146
149
  const provider = semanticReady
147
150
  ? withSemanticRequestControl(deps.capability.getProvider(deps.cwd), control)
148
151
  : createStructuralCodeProvider(deps.capability.getStructuralProvider(deps.cwd));
152
+ const semanticReadinessReason =
153
+ readiness.kind === "timeout" ? SEMANTIC_READINESS_TIMEOUT_REASON : undefined;
149
154
  const lspRuntime = semanticReady
150
155
  ? deps.capability.getLspRuntimeState(deps.cwd)
151
156
  : {
152
157
  kind: "unavailable" as const,
153
- reason: readiness.kind === "timeout" ? "Semantic readiness timed out" : readiness.reason,
158
+ reason: readiness.kind === "timeout" ? SEMANTIC_READINESS_TIMEOUT_REASON : readiness.reason,
154
159
  };
155
160
  const result = await executeOrientation(
156
161
  { target: entry, maxResults },
@@ -158,6 +163,7 @@ async function orientTarget(options: {
158
163
  model,
159
164
  provider,
160
165
  lspRuntime,
166
+ semanticReadinessReason,
161
167
  cwd: deps.cwd,
162
168
  requestControl: control,
163
169
  },
@@ -201,7 +207,7 @@ function addInstructionFiles(
201
207
  deps: OrientationWorkflowDeps,
202
208
  ): Awaited<ReturnType<typeof executeOrientation>> {
203
209
  if (!deps.projectTrusted || !isDirectory(focusPath)) return result;
204
- const config = loadCodeIntelligenceConfig(deps.cwd);
210
+ const config = loadCodeIntelligenceConfig(deps.cwd, deps.homeDir);
205
211
  const matches = findInstructionFilesForDirectory({
206
212
  directory: focusPath,
207
213
  cwd: deps.cwd,
@@ -7,6 +7,7 @@ import type {
7
7
  SourceRange,
8
8
  } from "@mrclrchtr/supi-code-runtime/api";
9
9
  import { toLspPosition } from "@mrclrchtr/supi-lsp/api";
10
+ import { SEMANTIC_READINESS_TIMEOUT_REASON } from "../analysis/readiness.ts";
10
11
  import { applyWorkspaceEdit } from "../analysis/refactor/apply.ts";
11
12
  import {
12
13
  establishMutationAuthority,
@@ -93,7 +94,7 @@ export async function runRefactorPlanWorkflow(
93
94
  control,
94
95
  );
95
96
  if (readiness.kind === "timeout") {
96
- return { kind: "unavailable", reason: "Semantic readiness timed out." };
97
+ return { kind: "unavailable", reason: SEMANTIC_READINESS_TIMEOUT_REASON };
97
98
  }
98
99
  if (readiness.kind === "unavailable") return readiness;
99
100
  throwIfAborted(control);
@@ -138,6 +138,8 @@ export class WorkspaceCodeIntelligenceSession {
138
138
 
139
139
  /** Whether the hidden architecture overview has been injected. */
140
140
  #hasInjectedOverview = false;
141
+ /** Pinned overview setting; null until resolved once per session. */
142
+ #overviewEnabled: boolean | null = null;
141
143
 
142
144
  /** Most recent explicit diagnostic refresh attempt, including its established outcome. */
143
145
  #lastHealthRefreshAttempt: HealthRefreshAttempt | null = null;
@@ -166,6 +168,9 @@ export class WorkspaceCodeIntelligenceSession {
166
168
  /** Whether the project owning this session is trusted for config loading. */
167
169
  #projectTrusted = false;
168
170
 
171
+ /** Global config home for hermetic config reads; undefined uses os.homedir(). */
172
+ #homeDir: string | undefined;
173
+
169
174
  constructor(cwd: string, capability?: CapabilityAdapter) {
170
175
  this.cwd = cwd;
171
176
  this.#capability = capability ?? new WorkspaceCapabilityAdapter();
@@ -176,6 +181,11 @@ export class WorkspaceCodeIntelligenceSession {
176
181
  this.#projectTrusted = trusted;
177
182
  }
178
183
 
184
+ /** Record the global-config home used for session config reads. */
185
+ setHomeDir(homeDir?: string): void {
186
+ this.#homeDir = homeDir;
187
+ }
188
+
179
189
  /** Attach lifecycle-owned LSP state without exposing it to Tool adapters. */
180
190
  attachLspController(controller: LspRuntimeController | null): void {
181
191
  this.#lspController = controller;
@@ -194,9 +204,22 @@ export class WorkspaceCodeIntelligenceSession {
194
204
  this.#hasInjectedOverview = true;
195
205
  }
196
206
 
207
+ /**
208
+ * Pin the overview setting resolved once per session. The first resolution
209
+ * wins; later config changes never toggle the setting mid-session.
210
+ */
211
+ setOverviewEnabledOnce(enabled: boolean): void {
212
+ this.#overviewEnabled ??= enabled;
213
+ }
214
+
215
+ /** Whether the overview setting has been pinned for this session. */
216
+ hasPinnedOverview(): boolean {
217
+ return this.#overviewEnabled !== null;
218
+ }
219
+
197
220
  /** Atomically claim first-turn overview injection for this workspace session. */
198
221
  claimOverviewInjection(): boolean {
199
- if (this.#hasInjectedOverview) return false;
222
+ if (this.#overviewEnabled === false || this.#hasInjectedOverview) return false;
200
223
  this.#hasInjectedOverview = true;
201
224
  return true;
202
225
  }
@@ -284,6 +307,7 @@ export class WorkspaceCodeIntelligenceSession {
284
307
  surfacedInstructionDirs: this.#surfacedInstructionDirs,
285
308
  markInstructionDirsSurfaced: (directories) => this.markInstructionDirsSurfaced(directories),
286
309
  projectTrusted: this.#projectTrusted,
310
+ homeDir: this.#homeDir,
287
311
  },
288
312
  control,
289
313
  );
@@ -13,16 +13,38 @@ import {
13
13
  isCodeRequestInterruption,
14
14
  throwIfCodeRequestInterrupted,
15
15
  } from "@mrclrchtr/supi-code-runtime/api";
16
+ import { isWithinOrEqual } from "@mrclrchtr/supi-core/api";
17
+ import { uriToFile } from "@mrclrchtr/supi-core/path";
16
18
  import type { DiagnosticEvidenceSummary } from "@mrclrchtr/supi-lsp/api";
17
19
  import {
18
- clearTsconfigCache,
20
+ FileChangeType,
21
+ type FileEvent,
22
+ invalidateTsconfigCacheForConfig,
23
+ invalidateTsconfigCacheForConfigDir,
19
24
  isLikelyStaleDiagnostic,
25
+ isProjectConfigFileName,
20
26
  raceRequestControl,
21
27
  syncWorkspaceSentinelSnapshot,
22
28
  type WorkspaceLspRuntime,
23
29
  } from "@mrclrchtr/supi-lsp/api";
24
30
  import { mergeDiagnosticEvidence } from "../../diagnostics/evidence.ts";
25
31
 
32
+ /** Options shared by the workspace and file-scoped maintenance passes. */
33
+ export interface LspMaintenanceOptions {
34
+ control?: CodeRequestControl;
35
+ /**
36
+ * Workspace-relative scope prefix for source-file discovery. Discovery only
37
+ * tracks created files inside this prefix; null disables the scope bound.
38
+ */
39
+ scope?: string | null;
40
+ /**
41
+ * Track files created since the last refresh so the following refresh pull
42
+ * includes them. Only the workspace-runtime path sets this; the snapshot is
43
+ * still widened on file-scoped passes so later diffs stay correct.
44
+ */
45
+ trackSources?: boolean;
46
+ }
47
+
26
48
  /**
27
49
  * Run sentinel-sync, diagnostic refresh, and stale-module resync as a single
28
50
  * maintenance pass. Call before explicit diagnostic queries (code_health with
@@ -32,28 +54,28 @@ export async function refreshLspMaintenance(
32
54
  runtime: WorkspaceLspRuntime,
33
55
  cwd: string,
34
56
  sentinelSnapshot: Map<string, number>,
35
- control?: CodeRequestControl,
57
+ options: LspMaintenanceOptions = {},
36
58
  ): Promise<WorkspaceLspMaintenanceResult> {
37
- const { snapshot } = synchronizeSentinels(runtime, cwd, sentinelSnapshot);
59
+ const { snapshot } = await synchronizeSentinels(runtime, cwd, sentinelSnapshot, options);
38
60
  let diagnosticEvidence = emptyEvidence();
39
61
  let failureReason: string | undefined;
40
62
 
41
63
  // Refresh before stale-module inspection. The refresh owns deleted-file
42
64
  // classification; later snapshots must not erase its removed evidence.
43
65
  try {
44
- diagnosticEvidence = await runtime.refreshOpenDiagnostics(undefined, control);
66
+ diagnosticEvidence = await runtime.refreshOpenDiagnostics(undefined, options.control);
45
67
  } catch (error) {
46
- if (isCodeRequestInterruption(error, control)) throw error;
68
+ if (isCodeRequestInterruption(error, options.control)) throw error;
47
69
  failureReason = errorMessage(error);
48
70
  diagnosticEvidence = readRuntimeEvidence(runtime);
49
71
  }
50
72
 
51
73
  try {
52
- const staleResult = await resyncStaleModuleFiles(runtime, cwd, control);
74
+ const staleResult = await resyncStaleModuleFiles(runtime, cwd, options.control);
53
75
  diagnosticEvidence = mergeDiagnosticEvidence(diagnosticEvidence, staleResult.evidence, cwd);
54
76
  if (staleResult.completed) failureReason = undefined;
55
77
  } catch (error) {
56
- if (isCodeRequestInterruption(error, control)) throw error;
78
+ if (isCodeRequestInterruption(error, options.control)) throw error;
57
79
  // Keep evidence from the first refresh when stale inspection fails.
58
80
  }
59
81
 
@@ -93,15 +115,13 @@ export async function refreshFileLspMaintenance(options: {
93
115
  const { runtime, cwd, sentinelSnapshot, filePath, control } = options;
94
116
  // A cancelled caller stops before any maintenance work starts.
95
117
  throwIfCodeRequestInterrupted(control);
96
- const { snapshot } = synchronizeSentinels(runtime, cwd, sentinelSnapshot);
118
+ const { snapshot } = await synchronizeSentinels(runtime, cwd, sentinelSnapshot);
97
119
  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
- );
120
+ const stale = confirmedOutstandingDiagnostics(runtime, cwd).some(
121
+ (entry) =>
122
+ nodePath.resolve(cwd, entry.file) === target &&
123
+ entry.diagnostics.some((diagnostic) => isLikelyStaleDiagnostic(diagnostic)),
124
+ );
105
125
 
106
126
  runtime.pruneMissingFiles();
107
127
  if (stale) {
@@ -116,29 +136,91 @@ export async function refreshFileLspMaintenance(options: {
116
136
  return { snapshot, matchedStaleFileCount: stale ? 1 : 0 };
117
137
  }
118
138
 
119
- function synchronizeSentinels(
139
+ /**
140
+ * Sync the widened workspace snapshot, forward sentinel changes, and track
141
+ * source files newly created since the last pass.
142
+ *
143
+ * The snapshot is widened to every regular file on every pass so later diffs
144
+ * see genuine creations. Only sentinel-file events are forwarded as workspace
145
+ * changes (config semantics unchanged); created source files are tracked only
146
+ * on the workspace-runtime path and only once the snapshot was primed by an
147
+ * earlier pass — the first pass establishes the baseline and cannot tell a
148
+ * just-created file from a long-existing one.
149
+ */
150
+ async function synchronizeSentinels(
120
151
  runtime: WorkspaceLspRuntime,
121
152
  cwd: string,
122
153
  sentinelSnapshot: Map<string, number>,
154
+ options: LspMaintenanceOptions = {},
123
155
  ) {
124
- const state = syncWorkspaceSentinelSnapshot(cwd, sentinelSnapshot);
125
- if (state.changes.length > 0) {
126
- clearTsconfigCache();
127
- runtime.noteWorkspaceChanges(state.changes);
156
+ const primed = sentinelSnapshot.size > 0;
157
+ const state = syncWorkspaceSentinelSnapshot(cwd, sentinelSnapshot, {
158
+ includeSourceFiles: true,
159
+ });
160
+
161
+ invalidateChangedProjectConfigs(state.changes);
162
+ if (state.changes.length > 0) runtime.noteWorkspaceChanges(state.changes);
163
+
164
+ if (options.trackSources && primed) {
165
+ await trackCreatedSourceFiles(runtime, state.sourceChanges, options.scope);
128
166
  }
167
+
129
168
  return state;
130
169
  }
131
170
 
132
- /** Re-open files with stale module-resolution errors. */
171
+ /** Invalidate cached tsconfig scope parses only for config files that changed. */
172
+ function invalidateChangedProjectConfigs(changes: readonly FileEvent[]): void {
173
+ for (const change of changes) {
174
+ const filePath = uriToFile(change.uri);
175
+ if (!isProjectConfigFileName(nodePath.basename(filePath))) continue;
176
+ // Change-targeted invalidation: only config files affect tsconfig scope
177
+ // decisions. A created config can also turn previously empty lookups into
178
+ // resolved ones below the new config's directory.
179
+ invalidateTsconfigCacheForConfig(filePath);
180
+ if (change.type === FileChangeType.Created) {
181
+ invalidateTsconfigCacheForConfigDir(nodePath.dirname(filePath));
182
+ }
183
+ }
184
+ }
185
+
186
+ /** Track source files newly created since the last pass, within the scope bound. */
187
+ async function trackCreatedSourceFiles(
188
+ runtime: WorkspaceLspRuntime,
189
+ sourceChanges: readonly FileEvent[],
190
+ scope: string | null | undefined,
191
+ ): Promise<void> {
192
+ for (const change of sourceChanges) {
193
+ if (change.type !== FileChangeType.Created) continue;
194
+ const filePath = uriToFile(change.uri);
195
+ if (scope && !isWithinOrEqual(scope, filePath)) continue;
196
+ // Best-effort: a file no client can serve stays untracked and is
197
+ // simply absent from evidence until a later explicit request.
198
+ await runtime.trackFile(filePath);
199
+ }
200
+ }
201
+
202
+ /** Return only diagnostics whose document evidence is confirmed. */
203
+ function confirmedOutstandingDiagnostics(runtime: WorkspaceLspRuntime, cwd: string) {
204
+ const outstanding = runtime.getOutstandingDiagnostics(1);
205
+ const confirmedFiles = new Set(
206
+ outstanding.evidence.documents
207
+ .filter((document) => document.status === "confirmed")
208
+ .map((document) => nodePath.resolve(cwd, document.file)),
209
+ );
210
+ return outstanding.entries.filter((entry) =>
211
+ confirmedFiles.has(nodePath.resolve(cwd, entry.file)),
212
+ );
213
+ }
214
+
215
+ /** Re-open files with confirmed stale module-resolution errors. */
133
216
  async function resyncStaleModuleFiles(
134
217
  runtime: WorkspaceLspRuntime,
135
218
  cwd: string,
136
219
  control?: CodeRequestControl,
137
220
  ): Promise<{ evidence: DiagnosticEvidenceSummary; completed: boolean }> {
138
- const outstanding = runtime.getOutstandingDiagnostics(1);
139
221
  const staleFiles: string[] = [];
140
222
 
141
- for (const entry of outstanding.entries) {
223
+ for (const entry of confirmedOutstandingDiagnostics(runtime, cwd)) {
142
224
  if (entry.diagnostics.some((d) => isLikelyStaleDiagnostic(d))) {
143
225
  staleFiles.push(entry.file);
144
226
  }
@@ -11,7 +11,11 @@ import type {
11
11
  ToolResultEvent,
12
12
  } from "@earendil-works/pi-coding-agent";
13
13
  import { fileToUri } from "@mrclrchtr/supi-core/path";
14
- import { clearTsconfigCache } from "@mrclrchtr/supi-lsp/api";
14
+ import {
15
+ invalidateTsconfigCacheForConfig,
16
+ invalidateTsconfigCacheForConfigDir,
17
+ isProjectConfigFileName,
18
+ } from "@mrclrchtr/supi-lsp/api";
15
19
  import type { LspAdapterState } from "./state.ts";
16
20
 
17
21
  /** Tool names whose successful results should trigger workspace change notifications. */
@@ -37,10 +41,12 @@ export function registerWorkspaceRecoveryHandler(pi: ExtensionAPI, state: LspAda
37
41
  const normalized = filePath.startsWith("@") ? filePath.slice(1) : filePath;
38
42
  const resolved = nodePath.resolve(cwd, normalized);
39
43
 
40
- // Invalidate tsconfig cache when config files change
41
- const ext = nodePath.extname(resolved).toLowerCase();
42
- if (ext === ".json" || ext === ".jsonc") {
43
- clearTsconfigCache();
44
+ // Invalidate the tsconfig scope cache only when a project config itself
45
+ // changed; other .json/.jsonc writes (package.json and friends) do not
46
+ // affect scope decisions and keep their cached parses.
47
+ if (isProjectConfigFileName(nodePath.basename(resolved))) {
48
+ invalidateTsconfigCacheForConfig(resolved);
49
+ invalidateTsconfigCacheForConfigDir(nodePath.dirname(resolved));
44
50
  }
45
51
 
46
52
  runtime.noteWorkspaceChanges([{ uri: fileToUri(resolved), type: 2 }]);
@@ -0,0 +1,23 @@
1
+ /** Thin Pi adapter for the session-owned code_find workflow. */
2
+
3
+ import type { FindMode, FindWorkflowInput } from "../../session/find-types.ts";
4
+ import type { CodeIntelResult, CodeIntelToolExecCtx } from "../../types/index.ts";
5
+ import { toWorkflowControl } from "../infra/workflow-control.ts";
6
+ import type { CodeFindAstKind } from "./ast-kinds.ts";
7
+ import { finishFindResult } from "./result.ts";
8
+
9
+ export interface CodeFindToolParams {
10
+ query: string;
11
+ scope?: string[];
12
+ mode: FindMode;
13
+ kind?: CodeFindAstKind;
14
+ maxResults?: number;
15
+ }
16
+
17
+ export async function executeFindTool(
18
+ params: CodeFindToolParams,
19
+ ctx: CodeIntelToolExecCtx,
20
+ ): Promise<CodeIntelResult> {
21
+ const outcome = await ctx.session.find(params as FindWorkflowInput, toWorkflowControl(ctx));
22
+ return finishFindResult(outcome, params.scope);
23
+ }
@@ -0,0 +1,8 @@
1
+ export const toolDescription =
2
+ 'Search source shape with mode:"ast" or LSP workspace symbols with mode:"semantic". Modes never silently fall back. Unscoped AST scans cover visible supported files, skipping hidden and generated/dependency directories.';
3
+
4
+ export const promptSnippet = "explicit structural or semantic code search";
5
+
6
+ export const promptGuidelines = [
7
+ "Use code_find for structural or semantic search evidence; use PI grep for literal/regex source search and code_graph references for symbol-identity relationships.",
8
+ ];
@@ -4,8 +4,8 @@ import {
4
4
  type EvidenceListMetadata,
5
5
  renderEvidenceListMetadataDisclosure,
6
6
  } from "../../analysis/evidence.ts";
7
- import type { FindResultAssembly } from "../result/find.ts";
8
7
  import { renderStructuredEmptyState, renderStructuredMatches } from "./markdown.ts";
8
+ import type { FindResultAssembly } from "./result.ts";
9
9
 
10
10
  export function renderFindResult(assembly: FindResultAssembly): string {
11
11
  const { outcome } = assembly;
@@ -10,9 +10,9 @@ import {
10
10
  assembleToolResult,
11
11
  type ResultProvenance,
12
12
  type ToolResultAssembly,
13
- } from "./assembly.ts";
14
- import { createToolDisplaySection } from "./display.ts";
15
- import type { SearchDetails, ToolDisplaySection } from "./types.ts";
13
+ } from "../result/assembly.ts";
14
+ import { createToolDisplaySection } from "../result/display.ts";
15
+ import type { SearchDetails, ToolDisplaySection } from "../result/types.ts";
16
16
 
17
17
  /** Presentation-neutral assembled find result. */
18
18
  export interface FindResultAssembly {
@@ -81,16 +81,9 @@ function findNextQueries(outcome: Extract<FindWorkflowOutcome, { kind: "complete
81
81
  if (outcome.data.kind === "semantic" && outcome.data.symbols.length === 0) {
82
82
  return [
83
83
  "If you know the file, use code_resolve with a file selector to enumerate document declarations",
84
- "Change mode only when you need a different code-aware evidence substrate",
85
84
  ];
86
85
  }
87
- if (outcome.data.kind === "ast" && outcome.data.astKind === "call") {
88
- return [
89
- "Use code_graph references on a resolved target for symbol-identity relationships",
90
- "Use PI grep for literal or regex source matches when it is active",
91
- ];
92
- }
93
- return ["Change mode only when you need a different code-aware evidence substrate"];
86
+ return [];
94
87
  }
95
88
 
96
89
  function createFindEvidence(outcome: Extract<FindWorkflowOutcome, { kind: "completed" }>): {
@@ -157,3 +150,35 @@ function findProvenance(
157
150
  ? [{ source: "semantic", capability: "workspace-symbol" }]
158
151
  : [{ source: "structural", capability: "tree-sitter" }];
159
152
  }
153
+
154
+ import type { CodeIntelResult, CodeIntelToolExecCtx } from "../../types/index.ts";
155
+ import { searchErrorResult } from "../result/errors.ts";
156
+ import { renderFindResult } from "./render.ts";
157
+
158
+ type FindOutcome = Awaited<ReturnType<CodeIntelToolExecCtx["session"]["find"]>>;
159
+
160
+ /** Assemble the final model-visible code_find result for one workflow outcome. */
161
+ export function finishFindResult(
162
+ outcome: FindOutcome,
163
+ scope: readonly string[] | undefined,
164
+ ): CodeIntelResult {
165
+ if (outcome.kind === "unavailable") throw new Error(outcome.reason);
166
+ if (outcome.kind === "invalid-input") {
167
+ return searchErrorResult(`**Error:** ${outcome.message}`, {
168
+ scope: Array.isArray(scope) ? scope.join(", ") : null,
169
+ nextQueries: ["Fix the search input and retry"],
170
+ message: outcome.message,
171
+ });
172
+ }
173
+
174
+ const assembly = assembleFindWorkflowResult(outcome);
175
+ return {
176
+ content: renderFindResult(assembly),
177
+ details: {
178
+ type: "search",
179
+ data: assembly.details,
180
+ status: "completed",
181
+ displaySections: assembly.displaySections,
182
+ },
183
+ };
184
+ }