@mrclrchtr/supi-code-intelligence 1.3.1 → 1.4.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 (103) hide show
  1. package/README.md +70 -32
  2. package/node_modules/@mrclrchtr/supi-core/README.md +52 -41
  3. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  4. package/node_modules/@mrclrchtr/supi-core/src/api.ts +13 -13
  5. package/node_modules/@mrclrchtr/supi-core/src/{config-settings.ts → config/config-settings.ts} +2 -2
  6. package/node_modules/@mrclrchtr/{supi-lsp/node_modules/@mrclrchtr/supi-core/src → supi-core/src/context}/context-provider-registry.ts +1 -1
  7. package/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -1
  8. package/node_modules/@mrclrchtr/supi-core/src/index.ts +13 -13
  9. package/node_modules/@mrclrchtr/{supi-lsp/node_modules/@mrclrchtr/supi-core/src → supi-core/src/settings}/settings-registry.ts +1 -1
  10. package/node_modules/@mrclrchtr/supi-lsp/README.md +58 -39
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +52 -41
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +13 -13
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{config-settings.ts → config/config-settings.ts} +2 -2
  15. package/node_modules/@mrclrchtr/{supi-core/src → supi-lsp/node_modules/@mrclrchtr/supi-core/src/context}/context-provider-registry.ts +1 -1
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -1
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +13 -13
  18. package/node_modules/@mrclrchtr/{supi-core/src → supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings}/settings-registry.ts +1 -1
  19. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -2
  20. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -3
  21. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-refresh.ts +1 -1
  22. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +27 -3
  23. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +61 -5
  24. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +244 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/src/{types.ts → config/types.ts} +4 -2
  26. package/node_modules/@mrclrchtr/supi-lsp/src/coordinates.ts +11 -0
  27. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-augmentation.ts +5 -5
  28. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-context.ts +115 -0
  29. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-display.ts +1 -1
  30. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +3 -2
  31. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostics.ts +1 -1
  32. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +1 -1
  33. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/suppression-diagnostics.ts +1 -1
  34. package/node_modules/@mrclrchtr/supi-lsp/src/{workspace-sentinels.ts → diagnostics/workspace-sentinels.ts} +2 -2
  35. package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +2 -23
  36. package/node_modules/@mrclrchtr/supi-lsp/src/index.ts +18 -5
  37. package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +72 -120
  38. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +1 -1
  39. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +4 -2
  40. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +10 -7
  41. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +1 -1
  42. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +158 -6
  43. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +202 -43
  44. package/node_modules/@mrclrchtr/supi-lsp/src/{lsp-state.ts → session/lsp-state.ts} +22 -11
  45. package/node_modules/@mrclrchtr/supi-lsp/src/{scanner.ts → session/scanner.ts} +3 -3
  46. package/node_modules/@mrclrchtr/supi-lsp/src/{service-registry.ts → session/service-registry.ts} +104 -12
  47. package/node_modules/@mrclrchtr/supi-lsp/src/{settings-registration.ts → session/settings-registration.ts} +1 -1
  48. package/node_modules/@mrclrchtr/supi-lsp/src/session/tree-persist.ts +75 -0
  49. package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +1 -1
  50. package/node_modules/@mrclrchtr/supi-lsp/src/tool/guidance.ts +138 -0
  51. package/node_modules/@mrclrchtr/supi-lsp/src/tool/names.ts +19 -0
  52. package/node_modules/@mrclrchtr/supi-lsp/src/{overrides.ts → tool/overrides.ts} +55 -24
  53. package/node_modules/@mrclrchtr/supi-lsp/src/tool/register-tools.ts +224 -0
  54. package/node_modules/@mrclrchtr/supi-lsp/src/tool/service-actions.ts +258 -0
  55. package/node_modules/@mrclrchtr/supi-lsp/src/{ui.ts → ui/ui.ts} +4 -4
  56. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +11 -0
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +46 -39
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +1 -1
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +1 -1
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +1 -1
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/src/{runtime.ts → session/runtime.ts} +3 -3
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/src/{session.ts → session/session.ts} +4 -4
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/src/{callees.ts → tool/callees.ts} +3 -3
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/src/{exports.ts → tool/exports.ts} +4 -4
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/src/{formatting.ts → tool/formatting.ts} +1 -1
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/guidance.ts +22 -0
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/src/{imports.ts → tool/imports.ts} +4 -4
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/src/{node-at.ts → tool/node-at.ts} +3 -3
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/src/{outline.ts → tool/outline.ts} +3 -3
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +6 -29
  71. package/package.json +4 -4
  72. package/src/actions/affected-action.ts +4 -4
  73. package/src/actions/brief-action.ts +12 -13
  74. package/src/actions/callees-action.ts +14 -10
  75. package/src/actions/callers-action.ts +4 -4
  76. package/src/actions/implementations-action.ts +4 -4
  77. package/src/code-intelligence.ts +1 -1
  78. package/src/pattern-structured.ts +20 -22
  79. package/src/providers/semantic-provider.ts +34 -0
  80. package/src/providers/structural-provider.ts +14 -0
  81. package/src/target-resolution.ts +26 -35
  82. package/src/tool/guidance.ts +21 -0
  83. package/node_modules/@mrclrchtr/supi-lsp/src/guidance.ts +0 -163
  84. package/node_modules/@mrclrchtr/supi-lsp/src/search-fallback.ts +0 -98
  85. package/node_modules/@mrclrchtr/supi-lsp/src/tool-actions.ts +0 -430
  86. package/node_modules/@mrclrchtr/supi-lsp/src/tree-persist.ts +0 -48
  87. package/node_modules/@mrclrchtr/supi-lsp/src/tsconfig-scope.ts +0 -156
  88. package/src/guidance.ts +0 -42
  89. /package/node_modules/@mrclrchtr/supi-core/src/{config.ts → config/config.ts} +0 -0
  90. /package/node_modules/@mrclrchtr/supi-core/src/{context-messages.ts → context/context-messages.ts} +0 -0
  91. /package/node_modules/@mrclrchtr/supi-core/src/{context-tag.ts → context/context-tag.ts} +0 -0
  92. /package/node_modules/@mrclrchtr/supi-core/src/{settings-command.ts → settings/settings-command.ts} +0 -0
  93. /package/node_modules/@mrclrchtr/supi-core/src/{settings-ui.ts → settings/settings-ui.ts} +0 -0
  94. /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{config.ts → config/config.ts} +0 -0
  95. /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{context-messages.ts → context/context-messages.ts} +0 -0
  96. /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{context-tag.ts → context/context-tag.ts} +0 -0
  97. /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{settings-command.ts → settings/settings-command.ts} +0 -0
  98. /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{settings-ui.ts → settings/settings-ui.ts} +0 -0
  99. /package/node_modules/@mrclrchtr/supi-lsp/src/{capabilities.ts → config/capabilities.ts} +0 -0
  100. /package/node_modules/@mrclrchtr/supi-lsp/src/{config.ts → config/config.ts} +0 -0
  101. /package/node_modules/@mrclrchtr/supi-lsp/src/{defaults.json → config/defaults.json} +0 -0
  102. /package/node_modules/@mrclrchtr/supi-lsp/src/{renderer.ts → ui/renderer.ts} +0 -0
  103. /package/node_modules/@mrclrchtr/supi-tree-sitter/src/{structure.ts → tool/structure.ts} +0 -0
@@ -2,12 +2,12 @@
2
2
  // biome-ignore-all lint/nursery/noExcessiveLinesPerFile: file-level and single-target affected flows share helpers to keep the blast-radius logic in one place
3
3
 
4
4
  import * as path from "node:path";
5
- import { getSessionLspService } from "@mrclrchtr/supi-lsp/api";
6
5
  import { buildArchitectureModel, findModuleForPath, getDependents } from "../architecture.ts";
7
6
  import {
8
7
  appendPrioritySignalsSection,
9
8
  summarizePrioritySignalsForFiles,
10
9
  } from "../prioritization-signals.ts";
10
+ import { getSemanticService } from "../providers/semantic-provider.ts";
11
11
  import { resolveTarget } from "../resolve-target.ts";
12
12
  import {
13
13
  escapeRegex,
@@ -188,12 +188,12 @@ async function gatherReferences(
188
188
  params: ActionParams,
189
189
  cwd: string,
190
190
  ): Promise<{ refs: GatheredRef[]; confidence: ConfidenceMode; externalCount: number }> {
191
- const lspState = getSessionLspService(cwd);
191
+ const lsp = await getSemanticService(cwd, { waitForReady: true });
192
192
  const refs: GatheredRef[] = [];
193
193
  let externalCount = 0;
194
194
 
195
- if (lspState.kind === "ready") {
196
- const lspRefs = await lspState.service.references(target.file, target.position);
195
+ if (lsp) {
196
+ const lspRefs = await lsp.references(target.file, target.position);
197
197
  if (lspRefs && lspRefs.length > 0) {
198
198
  const filtered = filterOutDeclaration(lspRefs, target.file, target.position);
199
199
  for (const ref of filtered) {
@@ -2,9 +2,10 @@
2
2
 
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
- import { createTreeSitterSession } from "@mrclrchtr/supi-tree-sitter/api";
5
+ import type { TreeSitterSession } from "@mrclrchtr/supi-tree-sitter/api";
6
6
  import { buildArchitectureModel, findModuleForPath } from "../architecture.ts";
7
7
  import { generateFocusedBrief, generateProjectBrief } from "../brief.ts";
8
+ import { withStructuralSession } from "../providers/structural-provider.ts";
8
9
  import { normalizePath } from "../search-helpers.ts";
9
10
  import type { ActionParams } from "../tool-actions.ts";
10
11
  import type { CodeIntelResult } from "../types.ts";
@@ -108,23 +109,21 @@ interface TreeSitterContextInput {
108
109
 
109
110
  async function addTreeSitterContext(input: TreeSitterContextInput): Promise<void> {
110
111
  const { lines, relPath, line1, char1, cwd } = input;
111
- let tsSession: ReturnType<typeof createTreeSitterSession> | null = null;
112
112
  try {
113
- tsSession = createTreeSitterSession(cwd);
114
- await addNodeContext(lines, tsSession, relPath, { line: line1, char: char1 });
115
- await addOutlineContext(lines, tsSession, relPath, line1);
116
- await addImportsContext(lines, tsSession, relPath);
117
- await addExportsContext(lines, tsSession, relPath);
113
+ await withStructuralSession(cwd, async (tsSession) => {
114
+ await addNodeContext(lines, tsSession, relPath, { line: line1, char: char1 });
115
+ await addOutlineContext(lines, tsSession, relPath, line1);
116
+ await addImportsContext(lines, tsSession, relPath);
117
+ await addExportsContext(lines, tsSession, relPath);
118
+ });
118
119
  } catch {
119
120
  // Tree-sitter not available
120
- } finally {
121
- tsSession?.dispose();
122
121
  }
123
122
  }
124
123
 
125
124
  async function addNodeContext(
126
125
  lines: string[],
127
- ts: ReturnType<typeof createTreeSitterSession>,
126
+ ts: TreeSitterSession,
128
127
  relPath: string,
129
128
  pos: { line: number; char: number },
130
129
  ): Promise<void> {
@@ -144,7 +143,7 @@ async function addNodeContext(
144
143
 
145
144
  async function addOutlineContext(
146
145
  lines: string[],
147
- ts: ReturnType<typeof createTreeSitterSession>,
146
+ ts: TreeSitterSession,
148
147
  relPath: string,
149
148
  line1: number,
150
149
  ): Promise<void> {
@@ -183,7 +182,7 @@ function getOutlinePrefix(kind: string): string {
183
182
 
184
183
  async function addImportsContext(
185
184
  lines: string[],
186
- ts: ReturnType<typeof createTreeSitterSession>,
185
+ ts: TreeSitterSession,
187
186
  relPath: string,
188
187
  ): Promise<void> {
189
188
  const result = await ts.imports(relPath);
@@ -201,7 +200,7 @@ async function addImportsContext(
201
200
 
202
201
  async function addExportsContext(
203
202
  lines: string[],
204
- ts: ReturnType<typeof createTreeSitterSession>,
203
+ ts: TreeSitterSession,
205
204
  relPath: string,
206
205
  ): Promise<void> {
207
206
  const result = await ts.exports(relPath);
@@ -3,7 +3,7 @@
3
3
  // extraction to @mrclrchtr/supi-tree-sitter/api.
4
4
 
5
5
  import * as path from "node:path";
6
- import { createTreeSitterSession } from "@mrclrchtr/supi-tree-sitter/api";
6
+ import { withStructuralSession } from "../providers/structural-provider.ts";
7
7
  import { resolveTarget } from "../resolve-target.ts";
8
8
  import { isResolvedTargetGroup } from "../semantic-action-helpers.ts";
9
9
  import type { ActionParams } from "../tool-actions.ts";
@@ -47,11 +47,11 @@ export async function executeCalleesAction(
47
47
  }
48
48
 
49
49
  const relPath = path.relative(cwd, target.file);
50
- let tsSession: ReturnType<typeof createTreeSitterSession> | null = null;
51
50
 
52
51
  try {
53
- tsSession = createTreeSitterSession(cwd);
54
- const result = await tsSession.calleesAt(relPath, target.displayLine, target.displayCharacter);
52
+ const result = await withStructuralSession(cwd, (tsSession) =>
53
+ tsSession.calleesAt(relPath, target.displayLine, target.displayCharacter),
54
+ );
55
55
 
56
56
  if (result.kind !== "success") {
57
57
  return {
@@ -63,7 +63,9 @@ export async function executeCalleesAction(
63
63
  scope: null,
64
64
  candidateCount: 0,
65
65
  omittedCount: 0,
66
- nextQueries: ["Use `lsp` for type-aware analysis on this file"],
66
+ nextQueries: [
67
+ 'Use `lsp_lookup` with `kind: "hover"` for type-aware analysis on this file',
68
+ ],
67
69
  },
68
70
  },
69
71
  };
@@ -95,7 +97,9 @@ export async function executeCalleesAction(
95
97
  scope: null,
96
98
  candidateCount: callees.length,
97
99
  omittedCount: Math.max(0, callees.length - (params.maxResults ?? 8)),
98
- nextQueries: ["Use `lsp` for precise type information on callees"],
100
+ nextQueries: [
101
+ 'Use `lsp_lookup` with `kind: "hover"` for precise type information on callees',
102
+ ],
99
103
  };
100
104
  return { content, details: { type: "search" as const, data: details } };
101
105
  } catch {
@@ -108,12 +112,12 @@ export async function executeCalleesAction(
108
112
  scope: null,
109
113
  candidateCount: 0,
110
114
  omittedCount: 0,
111
- nextQueries: ["Use `lsp` for type-aware analysis on this file"],
115
+ nextQueries: [
116
+ 'Use `lsp_lookup` with `kind: "hover"` for type-aware analysis on this file',
117
+ ],
112
118
  },
113
119
  },
114
120
  };
115
- } finally {
116
- tsSession?.dispose();
117
121
  }
118
122
  }
119
123
 
@@ -140,7 +144,7 @@ function formatCallees(
140
144
  }
141
145
  lines.push("");
142
146
  lines.push(
143
- "_Structural analysis — may include unresolved or qualified names. Use `lsp` for precise type information._",
147
+ '_Structural analysis — may include unresolved or qualified names. Use `lsp_lookup` with `kind: "hover"` for precise type information._',
144
148
  );
145
149
  lines.push("");
146
150
  return lines.join("\n");
@@ -1,7 +1,7 @@
1
1
  // Callers action — find call sites for a symbol.
2
2
 
3
3
  import * as path from "node:path";
4
- import { getSessionLspService } from "@mrclrchtr/supi-lsp/api";
4
+ import { getSemanticService } from "../providers/semantic-provider.ts";
5
5
  import { resolveTarget } from "../resolve-target.ts";
6
6
  import {
7
7
  escapeRegex,
@@ -169,10 +169,10 @@ async function collectCallerRefs(
169
169
  params: ActionParams,
170
170
  cwd: string,
171
171
  ): Promise<CallerCollection> {
172
- const lspState = getSessionLspService(cwd);
172
+ const lsp = await getSemanticService(cwd, { waitForReady: true });
173
173
 
174
- if (lspState.kind === "ready") {
175
- const refs = await lspState.service.references(target.file, target.position);
174
+ if (lsp) {
175
+ const refs = await lsp.references(target.file, target.position);
176
176
  if (refs && refs.length > 0) {
177
177
  const filtered = filterOutDeclaration(refs, target.file, target.position);
178
178
  const projectRefs: CallerRef[] = [];
@@ -1,7 +1,7 @@
1
1
  // Implementations action — find concrete implementations via LSP or heuristic.
2
2
 
3
3
  import * as path from "node:path";
4
- import { getSessionLspService } from "@mrclrchtr/supi-lsp/api";
4
+ import { getSemanticService } from "../providers/semantic-provider.ts";
5
5
  import { resolveTarget } from "../resolve-target.ts";
6
6
  import {
7
7
  escapeRegex,
@@ -52,11 +52,11 @@ export async function executeImplementationsAction(
52
52
  };
53
53
  }
54
54
 
55
- const lspState = getSessionLspService(cwd);
55
+ const lsp = await getSemanticService(cwd, { waitForReady: true });
56
56
  const relPath = path.relative(cwd, target.file);
57
57
 
58
- if (lspState.kind === "ready") {
59
- const impls = await lspState.service.implementation(target.file, target.position);
58
+ if (lsp) {
59
+ const impls = await lsp.implementation(target.file, target.position);
60
60
  if (impls) {
61
61
  const locations = Array.isArray(impls) ? impls : [impls];
62
62
  if (locations.length > 0) {
@@ -6,7 +6,7 @@ import type { BeforeAgentStartEventResult, ExtensionAPI } from "@earendil-works/
6
6
  import { Type } from "typebox";
7
7
  import { buildArchitectureModel } from "./architecture.ts";
8
8
  import { generateOverview } from "./brief.ts";
9
- import { promptGuidelines, promptSnippet, toolDescription } from "./guidance.ts";
9
+ import { promptGuidelines, promptSnippet, toolDescription } from "./tool/guidance.ts";
10
10
  import { type CodeIntelAction, executeAction } from "./tool-actions.ts";
11
11
 
12
12
  const OVERVIEW_CUSTOM_TYPE = "code-intelligence-overview";
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { createTreeSitterSession } from "@mrclrchtr/supi-tree-sitter/api";
3
+ import { withStructuralSession } from "./providers/structural-provider.ts";
4
4
  import type { ActionParams } from "./tool-actions.ts";
5
5
 
6
6
  export const STRUCTURED_PATTERN_FILE_CAP = 200;
@@ -42,31 +42,29 @@ export async function getStructuredPatternMatches(
42
42
  return matcher;
43
43
  }
44
44
 
45
- let tsSession: ReturnType<typeof createTreeSitterSession> | null = null;
46
45
  try {
47
- tsSession = createTreeSitterSession(cwd);
48
- const matches: StructuredMatch[] = [];
49
- let timedOut = collected.timedOut;
50
-
51
- for (const [index, file] of collected.files.entries()) {
52
- if (Date.now() > deadline) {
53
- collected.omittedCount += collected.files.length - index;
54
- timedOut = true;
55
- break;
46
+ return await withStructuralSession(cwd, async (tsSession) => {
47
+ const matches: StructuredMatch[] = [];
48
+ let timedOut = collected.timedOut;
49
+
50
+ for (const [index, file] of collected.files.entries()) {
51
+ if (Date.now() > deadline) {
52
+ collected.omittedCount += collected.files.length - index;
53
+ timedOut = true;
54
+ break;
55
+ }
56
+ const relFile = path.relative(cwd, file);
57
+ await collectMatchesForFile(matches, tsSession, relFile, params.kind, matcher);
56
58
  }
57
- const relFile = path.relative(cwd, file);
58
- await collectMatchesForFile(matches, tsSession, relFile, params.kind, matcher);
59
- }
60
59
 
61
- return {
62
- matches,
63
- omittedCount: timedOut ? Math.max(1, collected.omittedCount) : collected.omittedCount,
64
- partialReason: timedOut ? "timeout" : collected.omittedCount > 0 ? "file-cap" : null,
65
- };
60
+ return {
61
+ matches,
62
+ omittedCount: timedOut ? Math.max(1, collected.omittedCount) : collected.omittedCount,
63
+ partialReason: timedOut ? "timeout" : collected.omittedCount > 0 ? "file-cap" : null,
64
+ };
65
+ });
66
66
  } catch {
67
67
  return `No structured ${params.kind} search data available in \`${relScope}\`. Try omitting \`kind\` for plain text search.`;
68
- } finally {
69
- tsSession?.dispose();
70
68
  }
71
69
  }
72
70
 
@@ -74,7 +72,7 @@ export async function getStructuredPatternMatches(
74
72
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: kind-specific tree-sitter matching is clearest as one helper
75
73
  async function collectMatchesForFile(
76
74
  matches: StructuredMatch[],
77
- tsSession: ReturnType<typeof createTreeSitterSession>,
75
+ tsSession: import("@mrclrchtr/supi-tree-sitter/api").TreeSitterSession,
78
76
  relFile: string,
79
77
  kind: StructuredPatternKind,
80
78
  matcher: (value: string) => boolean,
@@ -0,0 +1,34 @@
1
+ import {
2
+ getSessionLspService,
3
+ type SessionLspService,
4
+ type SessionLspServiceState,
5
+ waitForSessionLspService,
6
+ } from "@mrclrchtr/supi-lsp/api";
7
+
8
+ export interface SemanticProviderOptions {
9
+ waitForReady?: boolean;
10
+ timeoutMs?: number;
11
+ }
12
+
13
+ export const DEFAULT_SEMANTIC_WAIT_MS = 250;
14
+
15
+ /** Acquire the current session-scoped LSP service, optionally waiting for startup. */
16
+ export async function getSemanticService(
17
+ cwd: string,
18
+ options: SemanticProviderOptions = {},
19
+ ): Promise<SessionLspService | null> {
20
+ const state = await getSemanticServiceState(cwd, options);
21
+ return state.kind === "ready" ? state.service : null;
22
+ }
23
+
24
+ /** Read the current LSP service state with an optional short wait for readiness. */
25
+ export async function getSemanticServiceState(
26
+ cwd: string,
27
+ options: SemanticProviderOptions = {},
28
+ ): Promise<SessionLspServiceState> {
29
+ const current = getSessionLspService(cwd);
30
+ if (!options.waitForReady || current.kind !== "pending") {
31
+ return current;
32
+ }
33
+ return waitForSessionLspService(cwd, options.timeoutMs ?? DEFAULT_SEMANTIC_WAIT_MS);
34
+ }
@@ -0,0 +1,14 @@
1
+ import { createTreeSitterSession, type TreeSitterSession } from "@mrclrchtr/supi-tree-sitter/api";
2
+
3
+ /** Run work against a short-lived Tree-sitter session and dispose it afterward. */
4
+ export async function withStructuralSession<T>(
5
+ cwd: string,
6
+ fn: (session: TreeSitterSession) => Promise<T>,
7
+ ): Promise<T> {
8
+ const session = createTreeSitterSession(cwd);
9
+ try {
10
+ return await fn(session);
11
+ } finally {
12
+ session.dispose();
13
+ }
14
+ }
@@ -5,12 +5,9 @@
5
5
  import * as fs from "node:fs";
6
6
  import * as path from "node:path";
7
7
  import { isWithinOrEqual } from "@mrclrchtr/supi-core/api";
8
- import {
9
- getSessionLspService,
10
- type Position,
11
- type SessionLspService,
12
- } from "@mrclrchtr/supi-lsp/api";
13
- import { createTreeSitterSession } from "@mrclrchtr/supi-tree-sitter/api";
8
+ import { type Position, type SessionLspService, toLspPosition } from "@mrclrchtr/supi-lsp/api";
9
+ import { getSemanticService, getSemanticServiceState } from "./providers/semantic-provider.ts";
10
+ import { withStructuralSession } from "./providers/structural-provider.ts";
14
11
  import { escapeRegex, normalizePath } from "./search-helpers.ts";
15
12
  import { highestConfidence } from "./semantic-action-helpers.ts";
16
13
  import type { ConfidenceMode, DisambiguationCandidate } from "./types.ts";
@@ -46,7 +43,7 @@ export { normalizePath } from "./search-helpers.ts";
46
43
  * Convert 1-based public coordinates to 0-based LSP Position.
47
44
  */
48
45
  export function toZeroBased(line: number, character: number): Position {
49
- return { line: line - 1, character: character - 1 };
46
+ return toLspPosition(line, character);
50
47
  }
51
48
 
52
49
  /**
@@ -146,16 +143,12 @@ export async function resolveSymbolTarget(
146
143
  exportedOnly?: boolean;
147
144
  },
148
145
  ): Promise<TargetResolutionResult> {
149
- const lspState = getSessionLspService(cwd);
146
+ const lspState = await getSemanticServiceState(cwd, { waitForReady: true });
150
147
 
151
148
  if (lspState.kind === "ready") {
152
149
  return resolveSymbolViaLsp(symbol, cwd, lspState.service, options);
153
150
  }
154
151
 
155
- if (lspState.kind === "pending") {
156
- // In v1, we may wait for LSP. For now, try structural fallback.
157
- }
158
-
159
152
  // Structural fallback via text search
160
153
  return resolveSymbolViaSearch(symbol, cwd, options);
161
154
  }
@@ -336,10 +329,10 @@ async function resolveFileTargetsViaLsp(
336
329
  cwd: string,
337
330
  structuralTargets: ResolvedTarget[] | null,
338
331
  ): Promise<ResolvedTarget[] | null> {
339
- const lspState = getSessionLspService(cwd);
340
- if (lspState.kind !== "ready") return null;
332
+ const lsp = await getSemanticService(cwd, { waitForReady: true });
333
+ if (!lsp) return null;
341
334
 
342
- const symbols = await lspState.service.documentSymbols(resolvedFile);
335
+ const symbols = await lsp.documentSymbols(resolvedFile);
343
336
  if (!symbols || symbols.length === 0) {
344
337
  return structuralTargets;
345
338
  }
@@ -378,30 +371,28 @@ async function resolveFileTargetsViaTreeSitter(
378
371
  resolvedFile: string,
379
372
  cwd: string,
380
373
  ): Promise<ResolvedTarget[] | null> {
381
- let tsSession: ReturnType<typeof createTreeSitterSession> | null = null;
382
374
  try {
383
- tsSession = createTreeSitterSession(cwd);
384
- const exportsResult = await tsSession.exports(relPath);
385
- if (exportsResult.kind !== "success" || exportsResult.data.length === 0) {
386
- return null;
387
- }
375
+ return await withStructuralSession(cwd, async (tsSession) => {
376
+ const exportsResult = await tsSession.exports(relPath);
377
+ if (exportsResult.kind !== "success" || exportsResult.data.length === 0) {
378
+ return null;
379
+ }
388
380
 
389
- return dedupeTargets(
390
- exportsResult.data.map((record) =>
391
- createResolvedTarget({
392
- file: resolvedFile,
393
- line: record.range.startLine,
394
- character: record.range.startCharacter,
395
- name: record.name,
396
- kind: record.kind,
397
- confidence: "structural",
398
- }),
399
- ),
400
- );
381
+ return dedupeTargets(
382
+ exportsResult.data.map((record) =>
383
+ createResolvedTarget({
384
+ file: resolvedFile,
385
+ line: record.range.startLine,
386
+ character: record.range.startCharacter,
387
+ name: record.name,
388
+ kind: record.kind,
389
+ confidence: "structural",
390
+ }),
391
+ ),
392
+ );
393
+ });
401
394
  } catch {
402
395
  return null;
403
- } finally {
404
- tsSession?.dispose();
405
396
  }
406
397
  }
407
398
 
@@ -0,0 +1,21 @@
1
+ // Prompt guidance and tool description for the code_intel tool.
2
+
3
+ export const toolDescription = `Code intelligence tool — codebase orientation, semantic relationships, impact analysis, and scoped search.
4
+
5
+ Actions: brief, callers, callees, implementations, affected, pattern, index.
6
+
7
+ Use code_intel to localize relevant files before precise drill-down: summarize a project/package/file, find callers/callees/implementations, estimate blast radius, or search within a scope. Prefer lsp_lookup, lsp_document_symbols, lsp_workspace_symbols, lsp_diagnostics, lsp_refactor, and lsp_recover for semantic drill-down once the target is known; use tree_sitter for exact syntax and read/rg once you know the file. line and character are 1-based and require file. pattern is literal unless regex is true; kind supports definition, export, or import. Relative paths resolve from cwd, and leading @ on path/file is stripped.`;
8
+
9
+ export const promptGuidelines = [
10
+ 'Use code_intel with `action: "brief"` for a project, package, directory, file, or anchored-position brief before opening more files.',
11
+ 'Use code_intel with `action: "index"` for a project map, top-level directories, language mix, or landmark files.',
12
+ 'Use code_intel with `action: "callers"` or `action: "implementations"` to find who invokes a symbol or which concrete types implement a declaration.',
13
+ 'Use code_intel with `action: "callees"` for outgoing calls from a function or method at a known `file`, `line`, and `character`.',
14
+ 'Use code_intel with `action: "affected"` before edits for blast radius, downstream modules, risk, and likely follow-up checks or tests.',
15
+ 'Use code_intel with `action: "pattern"` for bounded search within a path; `pattern` is literal by default, set `regex: true` for regex, and use `kind: "definition" | "export" | "import"` for structured search.',
16
+ "Use code_intel with `file`, `line`, and `character` for anchored positions; do not pair `line` or `character` with `path`.",
17
+ "Use code_intel first when the area is not yet localized; switch to lsp_lookup, lsp_document_symbols, lsp_workspace_symbols, lsp_diagnostics, lsp_refactor, or lsp_recover for semantic drill-down once code_intel narrows the target.",
18
+ ];
19
+
20
+ export const promptSnippet =
21
+ "code_intel — codebase orientation, callers/callees, blast radius, and scoped search before file drill-down";
@@ -1,163 +0,0 @@
1
- import * as path from "node:path";
2
- import { splitSuppressionDiagnostics } from "./diagnostics/suppression-diagnostics.ts";
3
- import type { OutstandingDiagnosticSummaryEntry } from "./manager/manager-types.ts";
4
- import type { Diagnostic, ProjectServerInfo } from "./types.ts";
5
-
6
- export const lspPromptSnippet =
7
- "Use semantic code intelligence for hover, definitions, references, symbols, rename planning, code actions, and diagnostics in supported languages.";
8
-
9
- export const lspPromptGuidelines = [
10
- "Prefer the lsp tool over bash text search for supported source files when the task is semantic code navigation or diagnostics.",
11
- "Use lsp for hover, definitions, references, document symbols, rename planning, code actions, and diagnostics before falling back to grep-style shell search.",
12
- "Fall back to bash/read when LSP is unavailable, the file type is unsupported, or the task is plain-text search across docs, config files, or string literals.",
13
- "Diagnostics are automatically delivered: inline after every write/edit tool result, and as context before each agent turn. You do not need to call the lsp tool to check them — they are already in your context.",
14
- "When delivered diagnostics show errors, decide: (a) expected temporary state from a planned multi-step change — continue your sequence, then verify at the end; (b) unexpected 'Cannot find module', unresolved imports, or type mismatches — stop and fix the root cause before editing more files.",
15
- "When the SAME error pattern appears across MULTIPLE files after you changed imports, dependencies, or shared types, it is a systemic root-cause issue (missing install, broken import path, wrong dependency version). Do not patch each file individually — find and fix the root cause first.",
16
- "When diagnostics look stale after package.json, lockfile, tsconfig, or generated-type changes, use lsp recover before editing more files.",
17
- "After changing package.json dependencies, imports, or peer dependencies, run the package manager install command (e.g., pnpm install) before concluding that module resolution errors are real code bugs.",
18
- ];
19
-
20
- /**
21
- * Build per-project `promptGuidelines` for the `lsp` tool registration.
22
- * These guidelines are part of pi's stable system prompt after session-start
23
- * tool registration, avoiding per-turn `before_agent_start` prompt overrides.
24
- */
25
- export function buildProjectGuidelines(servers: ProjectServerInfo[], cwd: string): string[] {
26
- const dynamic = servers.map((server) => {
27
- const root = displayRoot(server.root, cwd);
28
- const fileTypes = server.fileTypes.map((entry) => `.${entry}`).join(", ");
29
- const actions = server.supportedActions.join(", ");
30
- const status = server.status === "running" ? "active" : "unavailable";
31
- const actionText = actions.length > 0 ? ` | actions: ${actions}` : "";
32
- return `LSP ${status}: ${server.name} | root: ${root} | files: ${fileTypes}${actionText}`;
33
- });
34
-
35
- return [
36
- ...lspPromptGuidelines.slice(0, 2),
37
- "Use lsp before grep/rg/find for understanding code, finding usages, diagnostics, symbol lookup, and refactors in supported languages.",
38
- ...dynamic,
39
- "Use lsp actions by task: hover/definition/references/symbols for understanding code, references/workspace_symbol/search for usages, diagnostics/hover/code_actions for issues, and rename/code_actions for refactors.",
40
- ...lspPromptGuidelines.slice(2),
41
- ].filter(Boolean);
42
- }
43
-
44
- export const MAX_DETAILED_DIAGNOSTICS = 5;
45
- const MAX_DETAIL_LINES_PER_FILE = 3;
46
-
47
- interface DetailedDiagnostics {
48
- file: string;
49
- diagnostics: Diagnostic[];
50
- }
51
-
52
- export function formatDiagnosticsContext(
53
- diagnostics: OutstandingDiagnosticSummaryEntry[],
54
- maxFiles: number = 3,
55
- detailed?: DetailedDiagnostics[],
56
- staleWarning?: string | null,
57
- ): string | null {
58
- if (diagnostics.length === 0) return null;
59
-
60
- const totalDiags = diagnostics.reduce((sum, d) => sum + d.total, 0);
61
- const detailMap = buildDetailMap(diagnostics, totalDiags, detailed);
62
-
63
- const lines: string[] = [];
64
- if (staleWarning) lines.push(staleWarning);
65
- const visible = diagnostics.slice(0, maxFiles);
66
-
67
- for (const entry of visible) {
68
- lines.push(`- ${entry.file}: ${formatCounts(entry)}`);
69
- appendDetailLines(lines, detailMap?.get(entry.file));
70
- }
71
-
72
- const remaining = diagnostics.length - visible.length;
73
- if (remaining > 0) {
74
- lines.push(`- +${remaining} more file${remaining === 1 ? "" : "s"}`);
75
- }
76
-
77
- appendSuppressionCleanup(
78
- lines,
79
- visible.map((entry) => entry.file),
80
- detailMap,
81
- );
82
-
83
- return [
84
- '<extension-context source="supi-lsp">',
85
- "Outstanding diagnostics — fix these before proceeding:",
86
- ...lines,
87
- "</extension-context>",
88
- ].join("\n");
89
- }
90
-
91
- function buildDetailMap(
92
- _diagnostics: OutstandingDiagnosticSummaryEntry[],
93
- totalDiags: number,
94
- detailed?: DetailedDiagnostics[],
95
- ): Map<string, Diagnostic[]> | null {
96
- if (totalDiags > MAX_DETAILED_DIAGNOSTICS || !detailed || detailed.length === 0) return null;
97
- return new Map(detailed.map((d) => [d.file, d.diagnostics]));
98
- }
99
-
100
- function appendDetailLines(lines: string[], details?: Diagnostic[]): void {
101
- if (!details) return;
102
- for (const d of details.slice(0, MAX_DETAIL_LINES_PER_FILE)) {
103
- const line = d.range.start.line + 1;
104
- const char = d.range.start.character + 1;
105
- const source = d.source ? ` ${d.source}` : "";
106
- lines.push(` L${line} C${char}${source}: ${d.message}`);
107
- }
108
- if (details.length > MAX_DETAIL_LINES_PER_FILE) {
109
- const extra = details.length - MAX_DETAIL_LINES_PER_FILE;
110
- lines.push(` +${extra} more`);
111
- }
112
- }
113
-
114
- function appendSuppressionCleanup(
115
- lines: string[],
116
- visibleFiles: string[],
117
- detailMap: Map<string, Diagnostic[]> | null,
118
- ): void {
119
- if (!detailMap) return;
120
-
121
- const suppressionLines: string[] = [];
122
- for (const file of visibleFiles) {
123
- const diagnostics = detailMap.get(file);
124
- if (!diagnostics) continue;
125
-
126
- const { suppressions } = splitSuppressionDiagnostics(diagnostics, 1);
127
- if (suppressions.length === 0) continue;
128
-
129
- suppressionLines.push(`- ${file}`);
130
- appendDetailLines(suppressionLines, suppressions);
131
- }
132
-
133
- if (suppressionLines.length === 0) return;
134
- lines.push("", "Stale suppression comments — clean these up:", ...suppressionLines);
135
- }
136
-
137
- export function diagnosticsContextFingerprint(content: string | null): string | null {
138
- return content;
139
- }
140
-
141
- // reorderDiagnosticContextMessages, getContextToken, and findLastUserMessageIndex
142
- // have been extracted to supi-core/context-messages.ts.
143
- // Use pruneAndReorderContextMessages(messages, "lsp-context", activeToken) instead.
144
-
145
- function formatCounts(entry: OutstandingDiagnosticSummaryEntry): string {
146
- const counts: string[] = [];
147
- if (entry.errors > 0) counts.push(pluralize(entry.errors, "error"));
148
- if (entry.warnings > 0) counts.push(pluralize(entry.warnings, "warning"));
149
- if (entry.information > 0) counts.push(pluralize(entry.information, "info"));
150
- if (entry.hints > 0) counts.push(pluralize(entry.hints, "hint"));
151
- return counts.join(", ");
152
- }
153
-
154
- function displayRoot(root: string, cwd: string): string {
155
- const relative = path.relative(cwd, root);
156
- if (relative === "") return ".";
157
- if (relative.startsWith(`..${path.sep}`) || relative === "..") return root;
158
- return relative.replaceAll(path.sep, "/");
159
- }
160
-
161
- function pluralize(count: number, word: string): string {
162
- return `${count} ${word}${count === 1 ? "" : "s"}`;
163
- }