@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
@@ -0,0 +1,244 @@
1
+ // tsconfig-aware file scope detection.
2
+ //
3
+ // Determines whether a file is within the compilation scope of its nearest
4
+ // tsconfig.json or jsconfig.json using the TypeScript compiler's own config
5
+ // parsing APIs. Used by the diagnostic filter to suppress LSP errors on files
6
+ // that TypeScript itself would not include in the project.
7
+
8
+ import * as path from "node:path";
9
+ import ts from "typescript";
10
+
11
+ interface ParsedProjectConfig {
12
+ configPath: string;
13
+ configDir: string;
14
+ fileNames: Set<string>;
15
+ explicitFiles: Set<string> | null;
16
+ includeFilePattern: RegExp | null;
17
+ excludePattern: RegExp | null;
18
+ supportedExtensions: Set<string>;
19
+ usesDefaultInclude: boolean;
20
+ }
21
+
22
+ const nearestConfigCache = new Map<string, string | null>();
23
+ const parsedConfigCache = new Map<string, ParsedProjectConfig | null>();
24
+
25
+ const tsInternal = ts as typeof ts & {
26
+ getFileMatcherPatterns?: (
27
+ configDir: string,
28
+ excludes: readonly string[] | undefined,
29
+ includes: readonly string[] | undefined,
30
+ useCaseSensitiveFileNames: boolean,
31
+ currentDirectory: string,
32
+ ) => {
33
+ includeFilePattern?: string;
34
+ excludePattern?: string;
35
+ };
36
+ getSupportedExtensions?: (
37
+ options: ts.CompilerOptions,
38
+ extraFileExtensions?: unknown,
39
+ ) => ReadonlyArray<ReadonlyArray<string>>;
40
+ };
41
+
42
+ /**
43
+ * Check whether a file is excluded by its nearest tsconfig.json or jsconfig.json.
44
+ *
45
+ * @param filePath - Project-relative file path (e.g., "packages/foo/__tests__/x.test.ts")
46
+ * @param cwd - Absolute project root directory
47
+ * @returns `true` if the file is excluded from compilation scope
48
+ */
49
+ export function isFileExcludedByTsconfig(filePath: string, cwd: string): boolean {
50
+ const absolutePath = path.resolve(cwd, filePath);
51
+ const configPath = findNearestProjectConfig(path.dirname(absolutePath), cwd);
52
+ if (!configPath) return false;
53
+
54
+ const parsed = parseProjectConfig(configPath);
55
+ if (!parsed) return false;
56
+
57
+ return !isFileInProjectScope(parsed, absolutePath);
58
+ }
59
+
60
+ /**
61
+ * Find the nearest tsconfig.json or jsconfig.json walking upward from `startDir`,
62
+ * stopping at `rootDir`.
63
+ */
64
+ function findNearestProjectConfig(startDir: string, rootDir: string): string | null {
65
+ let dir = path.resolve(startDir);
66
+ const resolvedRoot = path.resolve(rootDir);
67
+
68
+ while (true) {
69
+ const cacheKey = `${normalizePath(dir)}::${normalizePath(resolvedRoot)}`;
70
+ const cached = nearestConfigCache.get(cacheKey);
71
+ if (cached !== undefined) return cached;
72
+
73
+ const configPath = getLocalProjectConfig(dir);
74
+ if (configPath) {
75
+ const resolvedConfigPath = path.resolve(configPath);
76
+ nearestConfigCache.set(cacheKey, resolvedConfigPath);
77
+ return resolvedConfigPath;
78
+ }
79
+
80
+ if (path.relative(resolvedRoot, dir).startsWith("..") || dir === resolvedRoot) {
81
+ nearestConfigCache.set(cacheKey, null);
82
+ return null;
83
+ }
84
+
85
+ const parent = path.dirname(dir);
86
+ if (parent === dir) {
87
+ nearestConfigCache.set(cacheKey, null);
88
+ return null;
89
+ }
90
+ dir = parent;
91
+ }
92
+ }
93
+
94
+ function getLocalProjectConfig(directory: string): string | null {
95
+ const tsconfigPath = path.join(directory, "tsconfig.json");
96
+ if (ts.sys.fileExists(tsconfigPath)) return tsconfigPath;
97
+
98
+ const jsconfigPath = path.join(directory, "jsconfig.json");
99
+ if (ts.sys.fileExists(jsconfigPath)) return jsconfigPath;
100
+
101
+ return null;
102
+ }
103
+
104
+ function parseProjectConfig(configPath: string): ParsedProjectConfig | null {
105
+ const normalizedConfigPath = normalizePath(configPath);
106
+ const cached = parsedConfigCache.get(normalizedConfigPath);
107
+ if (cached !== undefined) return cached;
108
+
109
+ const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, createParseConfigHost());
110
+ if (!parsed) {
111
+ parsedConfigCache.set(normalizedConfigPath, null);
112
+ return null;
113
+ }
114
+
115
+ const configDir = path.dirname(path.resolve(configPath));
116
+ const explicitFiles = extractExplicitFiles(parsed.raw.files, configDir);
117
+ const usesDefaultInclude = explicitFiles === null && !Array.isArray(parsed.raw.include);
118
+ const { includeFilePattern, excludePattern } = createFileMatchers(
119
+ configDir,
120
+ parsed.raw.include,
121
+ parsed.raw.exclude,
122
+ usesDefaultInclude,
123
+ );
124
+ const supportedExtensions = new Set(getSupportedExtensions(parsed.options));
125
+ if (parsed.options.resolveJsonModule) supportedExtensions.add(".json");
126
+
127
+ const result = {
128
+ configPath: path.resolve(configPath),
129
+ configDir,
130
+ fileNames: new Set(parsed.fileNames.map(normalizePath)),
131
+ explicitFiles,
132
+ includeFilePattern,
133
+ excludePattern,
134
+ supportedExtensions,
135
+ usesDefaultInclude,
136
+ } satisfies ParsedProjectConfig;
137
+ parsedConfigCache.set(normalizedConfigPath, result);
138
+ return result;
139
+ }
140
+
141
+ function extractExplicitFiles(rawFiles: unknown, configDir: string): Set<string> | null {
142
+ if (!Array.isArray(rawFiles)) return null;
143
+ return new Set(
144
+ rawFiles
145
+ .filter((entry): entry is string => typeof entry === "string")
146
+ .map((entry) => normalizePath(path.resolve(configDir, entry))),
147
+ );
148
+ }
149
+
150
+ function createFileMatchers(
151
+ configDir: string,
152
+ rawInclude: unknown,
153
+ rawExclude: unknown,
154
+ useDefaultInclude: boolean,
155
+ ): {
156
+ includeFilePattern: RegExp | null;
157
+ excludePattern: RegExp | null;
158
+ } {
159
+ const includeSpecs = Array.isArray(rawInclude)
160
+ ? rawInclude.filter((entry): entry is string => typeof entry === "string")
161
+ : undefined;
162
+ const excludeSpecs = Array.isArray(rawExclude)
163
+ ? rawExclude.filter((entry): entry is string => typeof entry === "string")
164
+ : undefined;
165
+ const matcherPatterns = getFileMatcherPatterns(
166
+ configDir,
167
+ excludeSpecs,
168
+ useDefaultInclude ? ["**/*"] : includeSpecs,
169
+ );
170
+
171
+ return {
172
+ includeFilePattern: matcherPatterns.includeFilePattern
173
+ ? new RegExp(matcherPatterns.includeFilePattern)
174
+ : null,
175
+ excludePattern: matcherPatterns.excludePattern
176
+ ? new RegExp(matcherPatterns.excludePattern)
177
+ : null,
178
+ };
179
+ }
180
+
181
+ function isFileInProjectScope(parsed: ParsedProjectConfig, absolutePath: string): boolean {
182
+ const normalizedPath = normalizePath(absolutePath);
183
+ if (parsed.fileNames.has(normalizedPath)) return true;
184
+
185
+ const extension = path.extname(absolutePath).toLowerCase();
186
+ if (!parsed.supportedExtensions.has(extension)) return false;
187
+
188
+ if (parsed.explicitFiles) return parsed.explicitFiles.has(normalizedPath);
189
+ if (!isWithinOrEqual(parsed.configDir, absolutePath)) return false;
190
+ if (parsed.excludePattern?.test(normalizedPath)) return false;
191
+ if (parsed.usesDefaultInclude) return true;
192
+ return parsed.includeFilePattern ? parsed.includeFilePattern.test(normalizedPath) : false;
193
+ }
194
+
195
+ function getSupportedExtensions(options: ts.CompilerOptions): string[] {
196
+ return tsInternal.getSupportedExtensions
197
+ ? [...tsInternal.getSupportedExtensions(options, undefined).flat()]
198
+ : [".ts", ".tsx", ".d.ts", ".cts", ".d.cts", ".mts", ".d.mts"];
199
+ }
200
+
201
+ function getFileMatcherPatterns(
202
+ configDir: string,
203
+ excludeSpecs: readonly string[] | undefined,
204
+ includeSpecs: readonly string[] | undefined,
205
+ ): { includeFilePattern?: string; excludePattern?: string } {
206
+ return tsInternal.getFileMatcherPatterns
207
+ ? tsInternal.getFileMatcherPatterns(
208
+ configDir,
209
+ excludeSpecs,
210
+ includeSpecs,
211
+ ts.sys.useCaseSensitiveFileNames,
212
+ path.parse(configDir).root,
213
+ )
214
+ : {};
215
+ }
216
+
217
+ function createParseConfigHost(): ts.ParseConfigFileHost {
218
+ return {
219
+ ...ts.sys,
220
+ onUnRecoverableConfigFileDiagnostic: () => {
221
+ // Treat invalid configs as unsupported rather than surfacing a secondary
222
+ // filter failure to the agent.
223
+ },
224
+ };
225
+ }
226
+
227
+ function isWithinOrEqual(root: string, target: string): boolean {
228
+ const relative = path.relative(root, target);
229
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== "..");
230
+ }
231
+
232
+ function normalizePath(target: string): string {
233
+ const resolved = path.resolve(target).replaceAll("\\", "/");
234
+ return ts.sys.useCaseSensitiveFileNames ? resolved : resolved.toLowerCase();
235
+ }
236
+
237
+ /**
238
+ * Clear cached nearest-config lookups and parsed project config state.
239
+ * Useful for testing and after workspace file changes.
240
+ */
241
+ export function clearTsconfigCache(): void {
242
+ nearestConfigCache.clear();
243
+ parsedConfigCache.clear();
244
+ }
@@ -348,16 +348,18 @@ export interface TextDocumentPositionParams {
348
348
 
349
349
  // ── JSON-RPC ──────────────────────────────────────────────────────────
350
350
 
351
+ export type JsonRpcId = number | string;
352
+
351
353
  export interface JsonRpcRequest {
352
354
  jsonrpc: "2.0";
353
- id: number;
355
+ id: JsonRpcId;
354
356
  method: string;
355
357
  params?: unknown;
356
358
  }
357
359
 
358
360
  export interface JsonRpcResponse {
359
361
  jsonrpc: "2.0";
360
- id: number;
362
+ id: JsonRpcId | null;
361
363
  result?: unknown;
362
364
  error?: { code: number; message: string; data?: unknown };
363
365
  }
@@ -0,0 +1,11 @@
1
+ import type { Position } from "./config/types.ts";
2
+
3
+ /** Convert public 1-based coordinates into a 0-based LSP position. */
4
+ export function toLspPosition(line: number, character: number): Position {
5
+ return { line: line - 1, character: character - 1 };
6
+ }
7
+
8
+ /** Convert a 0-based LSP position into 1-based display coordinates. */
9
+ export function toOneBasedPosition(position: Position): { line: number; character: number } {
10
+ return { line: position.line + 1, character: position.character + 1 };
11
+ }
@@ -1,6 +1,6 @@
1
- import * as path from "node:path";
1
+ import type { Diagnostic, Hover, MarkedString, MarkupContent } from "../config/types.ts";
2
2
  import type { LspManager } from "../manager/manager.ts";
3
- import type { Diagnostic, Hover, MarkedString, MarkupContent } from "../types.ts";
3
+ import { resolveSessionPath } from "../utils.ts";
4
4
 
5
5
  const AUGMENT_TIMEOUT_MS = 500;
6
6
 
@@ -12,13 +12,13 @@ export async function augmentDiagnostics(
12
12
  filePath: string,
13
13
  diags: Diagnostic[],
14
14
  manager: LspManager,
15
- _cwd: string,
15
+ cwd: string,
16
16
  ): Promise<string | null> {
17
17
  const firstError = diags.find((d) => d.severity === 1);
18
18
  if (!firstError) return null;
19
19
 
20
- const resolvedPath = path.resolve(filePath);
21
- const client = await manager.getClientForFile(filePath);
20
+ const resolvedPath = resolveSessionPath(cwd, filePath);
21
+ const client = await manager.getClientForFile(resolvedPath);
22
22
  if (!client) return null;
23
23
 
24
24
  const pos = firstError.range.start;
@@ -0,0 +1,115 @@
1
+ // Diagnostic context formatting for the LSP extension.
2
+ // Extracted from guidance.ts to keep prompt surfaces separate from formatting logic.
3
+
4
+ import type { Diagnostic } from "../config/types.ts";
5
+ import type { OutstandingDiagnosticSummaryEntry } from "../manager/manager-types.ts";
6
+ import { splitSuppressionDiagnostics } from "./suppression-diagnostics.ts";
7
+
8
+ export const MAX_DETAILED_DIAGNOSTICS = 5;
9
+ const MAX_DETAIL_LINES_PER_FILE = 3;
10
+
11
+ interface DetailedDiagnostics {
12
+ file: string;
13
+ diagnostics: Diagnostic[];
14
+ }
15
+
16
+ export function formatDiagnosticsContext(
17
+ diagnostics: OutstandingDiagnosticSummaryEntry[],
18
+ maxFiles: number = 3,
19
+ detailed?: DetailedDiagnostics[],
20
+ staleWarning?: string | null,
21
+ ): string | null {
22
+ if (diagnostics.length === 0) return null;
23
+
24
+ const totalDiags = diagnostics.reduce((sum, d) => sum + d.total, 0);
25
+ const detailMap = buildDetailMap(totalDiags, detailed);
26
+
27
+ const lines: string[] = [];
28
+ if (staleWarning) lines.push(staleWarning);
29
+ const visible = diagnostics.slice(0, maxFiles);
30
+
31
+ for (const entry of visible) {
32
+ lines.push(`- ${entry.file}: ${formatCounts(entry)}`);
33
+ appendDetailLines(lines, detailMap?.get(entry.file));
34
+ }
35
+
36
+ const remaining = diagnostics.length - visible.length;
37
+ if (remaining > 0) {
38
+ lines.push(`- +${remaining} more file${remaining === 1 ? "" : "s"}`);
39
+ }
40
+
41
+ appendSuppressionCleanup(
42
+ lines,
43
+ visible.map((entry) => entry.file),
44
+ detailMap,
45
+ );
46
+
47
+ return [
48
+ '<extension-context source="supi-lsp">',
49
+ "Outstanding diagnostics — fix these before proceeding:",
50
+ ...lines,
51
+ "</extension-context>",
52
+ ].join("\n");
53
+ }
54
+
55
+ function buildDetailMap(
56
+ totalDiags: number,
57
+ detailed?: DetailedDiagnostics[],
58
+ ): Map<string, Diagnostic[]> | null {
59
+ if (totalDiags > MAX_DETAILED_DIAGNOSTICS || !detailed || detailed.length === 0) return null;
60
+ return new Map(detailed.map((d) => [d.file, d.diagnostics]));
61
+ }
62
+
63
+ function appendDetailLines(lines: string[], details?: Diagnostic[]): void {
64
+ if (!details) return;
65
+ for (const d of details.slice(0, MAX_DETAIL_LINES_PER_FILE)) {
66
+ const line = d.range.start.line + 1;
67
+ const char = d.range.start.character + 1;
68
+ const source = d.source ? ` ${d.source}` : "";
69
+ lines.push(` L${line} C${char}${source}: ${d.message}`);
70
+ }
71
+ if (details.length > MAX_DETAIL_LINES_PER_FILE) {
72
+ const extra = details.length - MAX_DETAIL_LINES_PER_FILE;
73
+ lines.push(` +${extra} more`);
74
+ }
75
+ }
76
+
77
+ function appendSuppressionCleanup(
78
+ lines: string[],
79
+ visibleFiles: string[],
80
+ detailMap: Map<string, Diagnostic[]> | null,
81
+ ): void {
82
+ if (!detailMap) return;
83
+
84
+ const suppressionLines: string[] = [];
85
+ for (const file of visibleFiles) {
86
+ const diagnostics = detailMap.get(file);
87
+ if (!diagnostics) continue;
88
+
89
+ const { suppressions } = splitSuppressionDiagnostics(diagnostics, 1);
90
+ if (suppressions.length === 0) continue;
91
+
92
+ suppressionLines.push(`- ${file}`);
93
+ appendDetailLines(suppressionLines, suppressions);
94
+ }
95
+
96
+ if (suppressionLines.length === 0) return;
97
+ lines.push("", "Stale suppression comments — clean these up:", ...suppressionLines);
98
+ }
99
+
100
+ export function diagnosticsContextFingerprint(content: string | null): string | null {
101
+ return content;
102
+ }
103
+
104
+ function formatCounts(entry: OutstandingDiagnosticSummaryEntry): string {
105
+ const counts: string[] = [];
106
+ if (entry.errors > 0) counts.push(pluralize(entry.errors, "error"));
107
+ if (entry.warnings > 0) counts.push(pluralize(entry.warnings, "warning"));
108
+ if (entry.information > 0) counts.push(pluralize(entry.information, "info"));
109
+ if (entry.hints > 0) counts.push(pluralize(entry.hints, "hint"));
110
+ return counts.join(", ");
111
+ }
112
+
113
+ function pluralize(count: number, word: string): string {
114
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
115
+ }
@@ -1,5 +1,5 @@
1
+ import type { Diagnostic } from "../config/types.ts";
1
2
  import type { OutstandingDiagnosticSummaryEntry } from "../manager/manager-types.ts";
2
- import type { Diagnostic } from "../types.ts";
3
3
 
4
4
  export function formatDiagnosticsDisplayContent(
5
5
  diagnostics: OutstandingDiagnosticSummaryEntry[],
@@ -1,7 +1,8 @@
1
+ import { type Diagnostic, DiagnosticSeverity } from "../config/types.ts";
1
2
  import type { OutstandingDiagnosticSummaryEntry } from "../manager/manager-types.ts";
2
3
  import { isGlobMatch } from "../pattern-matcher.ts";
3
4
  import { displayRelativeFilePath, shouldIgnoreLspPath } from "../summary.ts";
4
- import { type Diagnostic, DiagnosticSeverity } from "../types.ts";
5
+ import { uriToFile } from "../utils.ts";
5
6
 
6
7
  export function collectDiagnosticSummaryCounts(
7
8
  fileDiags: Map<string, { errors: number; warnings: number }>,
@@ -52,7 +53,7 @@ export function accumulateOutstandingDiagnostics(
52
53
  }
53
54
 
54
55
  export function relativeFilePathFromUri(uri: string, cwd: string): string {
55
- return displayRelativeFilePath(uri.replace("file://", ""), cwd);
56
+ return displayRelativeFilePath(uriToFile(uri), cwd);
56
57
  }
57
58
 
58
59
  function isDiagnosticWithinThreshold(
@@ -1,7 +1,7 @@
1
1
  // Diagnostic formatting and severity utilities.
2
2
 
3
3
  import * as path from "node:path";
4
- import type { Diagnostic } from "../types.ts";
4
+ import type { Diagnostic } from "../config/types.ts";
5
5
 
6
6
  /** Map severity number to label. */
7
7
  export function severityLabel(severity: number | undefined): string {
@@ -1,4 +1,4 @@
1
- import type { Diagnostic } from "../types.ts";
1
+ import type { Diagnostic } from "../config/types.ts";
2
2
 
3
3
  export interface StaleDiagnosticAssessment {
4
4
  suspected: boolean;
@@ -1,4 +1,4 @@
1
- import type { Diagnostic } from "../types.ts";
1
+ import type { Diagnostic } from "../config/types.ts";
2
2
 
3
3
  const SUPPRESSION_WARNING_SEVERITY = 2;
4
4
 
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { FileChangeType, type FileEvent } from "./types.ts";
4
- import { fileToUri } from "./utils.ts";
3
+ import { FileChangeType, type FileEvent } from "../config/types.ts";
4
+ import { fileToUri } from "../utils.ts";
5
5
 
6
6
  const IGNORED_DIRECTORIES = new Set(["node_modules", ".pnpm", ".git", "dist", "coverage"]);
7
7
  const ROOT_LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"];
@@ -1,8 +1,6 @@
1
1
  // LSP result formatting — converts LSP response types into readable text.
2
2
 
3
3
  import * as path from "node:path";
4
- import type { GrepMatch } from "./search-fallback.ts";
5
- import { isProjectSource } from "./summary.ts";
6
4
  import type {
7
5
  CodeAction,
8
6
  DocumentSymbol,
@@ -13,7 +11,8 @@ import type {
13
11
  MarkupContent,
14
12
  SymbolInformation,
15
13
  WorkspaceEdit,
16
- } from "./types.ts";
14
+ } from "./config/types.ts";
15
+ import { isProjectSource } from "./summary.ts";
17
16
  import { uriToFile } from "./utils.ts";
18
17
 
19
18
  // ── Hover ─────────────────────────────────────────────────────────────
@@ -297,26 +296,6 @@ export function formatWorkspaceSymbols(symbols: SymbolInformation[], cwd: string
297
296
  return lines.join("\n");
298
297
  }
299
298
 
300
- // ── Search Results ────────────────────────────────────────────────────
301
-
302
- export function formatSearchResults(
303
- lspSymbols: SymbolInformation[] | null,
304
- grepMatches: GrepMatch[] | null,
305
- cwd: string,
306
- ): string {
307
- if (lspSymbols && lspSymbols.length > 0) {
308
- return formatWorkspaceSymbols(lspSymbols, cwd);
309
- }
310
- if (grepMatches && grepMatches.length > 0) {
311
- const lines = [`Text search results (${grepMatches.length}):\n`];
312
- for (const match of grepMatches) {
313
- lines.push(`- ${match.file}:${match.line}: ${match.text}`);
314
- }
315
- return lines.join("\n");
316
- }
317
- return "No symbols or text matches found.";
318
- }
319
-
320
299
  // ── Symbol Kind Names ─────────────────────────────────────────────────
321
300
 
322
301
  const SYMBOL_KIND_NAMES: Record<number, string> = {
@@ -1,10 +1,9 @@
1
1
  // Public library entrypoint for @mrclrchtr/supi-lsp/api.
2
- // Import from the package root to reuse session-scoped LSP services
3
- // without reaching into private implementation files.
2
+ // Consumers should import the published API surface from
3
+ // `@mrclrchtr/supi-lsp/api`, not the package root.
4
4
 
5
- export type { SessionLspServiceState } from "./service-registry.ts";
6
- export { getSessionLspService, SessionLspService } from "./service-registry.ts";
7
5
  export type {
6
+ CodeAction,
8
7
  Diagnostic,
9
8
  DocumentSymbol,
10
9
  Hover,
@@ -12,6 +11,20 @@ export type {
12
11
  LocationLink,
13
12
  Position,
14
13
  ProjectServerInfo,
14
+ Range,
15
15
  SymbolInformation,
16
+ WorkspaceEdit,
16
17
  WorkspaceSymbol,
17
- } from "./types.ts";
18
+ } from "./config/types.ts";
19
+ export { toLspPosition, toOneBasedPosition } from "./coordinates.ts";
20
+ export type {
21
+ OutstandingDiagnosticSummaryEntry,
22
+ RecoverDiagnosticsResult,
23
+ SessionLspServiceState,
24
+ WorkspaceDiagnosticSummaryEntry,
25
+ } from "./session/service-registry.ts";
26
+ export {
27
+ getSessionLspService,
28
+ SessionLspService,
29
+ waitForSessionLspService,
30
+ } from "./session/service-registry.ts";