@mrclrchtr/supi-code-intelligence 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +212 -0
  2. package/node_modules/@mrclrchtr/supi-core/README.md +90 -0
  3. package/node_modules/@mrclrchtr/supi-core/package.json +30 -0
  4. package/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
  5. package/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
  6. package/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
  7. package/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
  10. package/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
  11. package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
  12. package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
  13. package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
  14. package/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
  15. package/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
  16. package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
  17. package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
  18. package/node_modules/@mrclrchtr/supi-lsp/README.md +112 -0
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +90 -0
  20. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +30 -0
  21. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
  23. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
  24. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
  26. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
  27. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
  28. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
  29. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
  30. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
  31. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
  32. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
  34. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
  35. package/node_modules/@mrclrchtr/supi-lsp/package.json +45 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/src/capabilities.ts +62 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-refresh.ts +229 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +545 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +192 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/config.ts +143 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/defaults.json +82 -0
  42. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-augmentation.ts +82 -0
  43. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-display.ts +68 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +73 -0
  45. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostics.ts +98 -0
  46. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +47 -0
  47. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/suppression-diagnostics.ts +58 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +359 -0
  49. package/node_modules/@mrclrchtr/supi-lsp/src/guidance.ts +163 -0
  50. package/node_modules/@mrclrchtr/supi-lsp/src/index.ts +17 -0
  51. package/node_modules/@mrclrchtr/supi-lsp/src/lsp-state.ts +82 -0
  52. package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +470 -0
  53. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +34 -0
  54. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +139 -0
  55. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +39 -0
  56. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +46 -0
  57. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-types.ts +39 -0
  58. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +83 -0
  59. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +18 -0
  60. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +550 -0
  61. package/node_modules/@mrclrchtr/supi-lsp/src/overrides.ts +173 -0
  62. package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +197 -0
  63. package/node_modules/@mrclrchtr/supi-lsp/src/renderer.ts +120 -0
  64. package/node_modules/@mrclrchtr/supi-lsp/src/scanner.ts +153 -0
  65. package/node_modules/@mrclrchtr/supi-lsp/src/search-fallback.ts +98 -0
  66. package/node_modules/@mrclrchtr/supi-lsp/src/service-registry.ts +153 -0
  67. package/node_modules/@mrclrchtr/supi-lsp/src/settings-registration.ts +292 -0
  68. package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +153 -0
  69. package/node_modules/@mrclrchtr/supi-lsp/src/tool-actions.ts +430 -0
  70. package/node_modules/@mrclrchtr/supi-lsp/src/tree-persist.ts +48 -0
  71. package/node_modules/@mrclrchtr/supi-lsp/src/tsconfig-scope.ts +156 -0
  72. package/node_modules/@mrclrchtr/supi-lsp/src/types.ts +409 -0
  73. package/node_modules/@mrclrchtr/supi-lsp/src/ui.ts +358 -0
  74. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +122 -0
  75. package/node_modules/@mrclrchtr/supi-lsp/src/workspace-sentinels.ts +114 -0
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +97 -0
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +67 -0
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/.gitkeep +0 -0
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm +0 -0
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm.json +7 -0
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm +0 -0
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm.json +7 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm +0 -0
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm.json +7 -0
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm +0 -0
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm.json +7 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm +0 -0
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm.json +7 -0
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm +0 -0
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm.json +7 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm +0 -0
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm.json +7 -0
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +12 -0
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm +0 -0
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm.json +7 -0
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm +0 -0
  98. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm.json +7 -0
  99. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm +0 -0
  100. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm.json +7 -0
  101. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm +0 -0
  102. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm.json +7 -0
  103. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
  104. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +19 -0
  105. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm +0 -0
  106. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm.json +7 -0
  107. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm +0 -0
  108. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm.json +7 -0
  109. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +126 -0
  110. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +144 -0
  111. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +151 -0
  112. package/node_modules/@mrclrchtr/supi-tree-sitter/src/callees.ts +343 -0
  113. package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +108 -0
  114. package/node_modules/@mrclrchtr/supi-tree-sitter/src/exports.ts +315 -0
  115. package/node_modules/@mrclrchtr/supi-tree-sitter/src/formatting.ts +104 -0
  116. package/node_modules/@mrclrchtr/supi-tree-sitter/src/imports.ts +42 -0
  117. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +16 -0
  118. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +116 -0
  119. package/node_modules/@mrclrchtr/supi-tree-sitter/src/node-at.ts +96 -0
  120. package/node_modules/@mrclrchtr/supi-tree-sitter/src/outline.ts +287 -0
  121. package/node_modules/@mrclrchtr/supi-tree-sitter/src/runtime.ts +237 -0
  122. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session.ts +112 -0
  123. package/node_modules/@mrclrchtr/supi-tree-sitter/src/structure.ts +7 -0
  124. package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +13 -0
  125. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +306 -0
  126. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +146 -0
  127. package/package.json +47 -0
  128. package/src/actions/affected-action.ts +310 -0
  129. package/src/actions/brief-action.ts +242 -0
  130. package/src/actions/callees-action.ts +134 -0
  131. package/src/actions/callers-action.ts +215 -0
  132. package/src/actions/implementations-action.ts +190 -0
  133. package/src/actions/index-action.ts +187 -0
  134. package/src/actions/pattern-action.ts +232 -0
  135. package/src/architecture.ts +367 -0
  136. package/src/brief-focused.ts +383 -0
  137. package/src/brief.ts +228 -0
  138. package/src/code-intelligence.ts +122 -0
  139. package/src/git-context.ts +65 -0
  140. package/src/guidance.ts +39 -0
  141. package/src/index.ts +28 -0
  142. package/src/resolve-target.ts +104 -0
  143. package/src/search-helpers.ts +283 -0
  144. package/src/target-resolution.ts +368 -0
  145. package/src/tool-actions.ts +109 -0
  146. package/src/types.ts +57 -0
@@ -0,0 +1,98 @@
1
+ // Diagnostic formatting and severity utilities.
2
+
3
+ import * as path from "node:path";
4
+ import type { Diagnostic } from "../types.ts";
5
+
6
+ /** Map severity number to label. */
7
+ export function severityLabel(severity: number | undefined): string {
8
+ switch (severity) {
9
+ case 1:
10
+ return "error";
11
+ case 2:
12
+ return "warning";
13
+ case 3:
14
+ return "info";
15
+ case 4:
16
+ return "hint";
17
+ default:
18
+ return "unknown";
19
+ }
20
+ }
21
+
22
+ /** Map severity number to emoji. */
23
+ function severityIcon(severity: number | undefined): string {
24
+ switch (severity) {
25
+ case 1:
26
+ return "❌";
27
+ case 2:
28
+ return "⚠️";
29
+ case 3:
30
+ return "ℹ️";
31
+ case 4:
32
+ return "💡";
33
+ default:
34
+ return "❓";
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Format a single diagnostic for display.
40
+ * Output: "❌ error [source] (line:col): message"
41
+ */
42
+ export function formatDiagnostic(diag: Diagnostic): string {
43
+ const icon = severityIcon(diag.severity);
44
+ const sev = severityLabel(diag.severity);
45
+ const line = diag.range.start.line + 1; // Convert 0-based to 1-based
46
+ const col = diag.range.start.character + 1;
47
+ const source = diag.source ? ` [${diag.source}]` : "";
48
+ const code = diag.code !== undefined ? ` (${diag.code})` : "";
49
+ return `${icon} ${sev}${source}${code} (${line}:${col}): ${diag.message}`;
50
+ }
51
+
52
+ /**
53
+ * Format a list of diagnostics for a file.
54
+ */
55
+ export function formatDiagnostics(
56
+ filePath: string,
57
+ diagnostics: Diagnostic[],
58
+ cwd: string,
59
+ ): string {
60
+ if (diagnostics.length === 0) return "No diagnostics.";
61
+
62
+ const relPath = path.relative(cwd, filePath);
63
+ const lines = [`**${relPath}**:`];
64
+
65
+ for (const diag of diagnostics) {
66
+ lines.push(` ${formatDiagnostic(diag)}`);
67
+ }
68
+
69
+ return lines.join("\n");
70
+ }
71
+
72
+ /**
73
+ * Format diagnostics grouped by file.
74
+ */
75
+ export function formatGroupedDiagnostics(
76
+ entries: Array<{ file: string; diagnostics: Diagnostic[] }>,
77
+ cwd: string,
78
+ ): string {
79
+ if (entries.length === 0) return "No diagnostics across any files.";
80
+
81
+ const sections: string[] = [];
82
+ for (const entry of entries) {
83
+ if (entry.diagnostics.length > 0) {
84
+ sections.push(formatDiagnostics(entry.file, entry.diagnostics, cwd));
85
+ }
86
+ }
87
+
88
+ return sections.length > 0 ? sections.join("\n\n") : "No diagnostics.";
89
+ }
90
+
91
+ /**
92
+ * Filter diagnostics by severity threshold.
93
+ * Returns diagnostics with severity <= maxSeverity.
94
+ * Severity 1 = error, 2 = warning, 3 = info, 4 = hint.
95
+ */
96
+ export function filterBySeverity(diagnostics: Diagnostic[], maxSeverity: number): Diagnostic[] {
97
+ return diagnostics.filter((d) => d.severity !== undefined && d.severity <= maxSeverity);
98
+ }
@@ -0,0 +1,47 @@
1
+ import type { Diagnostic } from "../types.ts";
2
+
3
+ export interface StaleDiagnosticAssessment {
4
+ suspected: boolean;
5
+ matchedFiles: Array<{ file: string; diagnostics: Diagnostic[] }>;
6
+ warning: string | null;
7
+ }
8
+
9
+ const MODULE_RESOLUTION_MESSAGE =
10
+ /cannot find module|cannot find package|cannot resolve module|module not found/i;
11
+ const MODULE_RESOLUTION_CODES = new Set([2307, 2792]);
12
+
13
+ /** Assess whether a diagnostic cluster looks stale after a workspace change. */
14
+ export function assessStaleDiagnostics(
15
+ entries: Array<{ file: string; diagnostics: Diagnostic[] }>,
16
+ ): StaleDiagnosticAssessment {
17
+ const matchedFiles = entries
18
+ .map((entry) => ({
19
+ file: entry.file,
20
+ diagnostics: entry.diagnostics.filter(isLikelyStaleDiagnostic),
21
+ }))
22
+ .filter((entry) => entry.diagnostics.length > 0);
23
+
24
+ const suspected = matchedFiles.length >= 3;
25
+ return {
26
+ suspected,
27
+ matchedFiles,
28
+ warning: suspected
29
+ ? `⚠️ LSP diagnostics may be stale — ${matchedFiles.length} files report missing-module errors after a workspace change.`
30
+ : null,
31
+ };
32
+ }
33
+
34
+ function isLikelyStaleDiagnostic(diagnostic: Diagnostic): boolean {
35
+ if (diagnostic.severity === undefined) return false;
36
+ if (diagnostic.code !== undefined) {
37
+ if (typeof diagnostic.code === "number" && MODULE_RESOLUTION_CODES.has(diagnostic.code)) {
38
+ return true;
39
+ }
40
+ if (typeof diagnostic.code === "string") {
41
+ const parsed = Number.parseInt(diagnostic.code, 10);
42
+ if (MODULE_RESOLUTION_CODES.has(parsed)) return true;
43
+ }
44
+ }
45
+
46
+ return MODULE_RESOLUTION_MESSAGE.test(diagnostic.message);
47
+ }
@@ -0,0 +1,58 @@
1
+ import type { Diagnostic } from "../types.ts";
2
+
3
+ const SUPPRESSION_WARNING_SEVERITY = 2;
4
+
5
+ /** Detect diagnostics that represent stale suppression comments. */
6
+ export function isStaleSuppressionDiagnostic(diagnostic: Diagnostic): boolean {
7
+ const message = diagnostic.message.toLowerCase();
8
+
9
+ if (message.includes("unused '@ts-expect-error' directive")) {
10
+ return true;
11
+ }
12
+
13
+ if (diagnostic.source !== "biome") {
14
+ return false;
15
+ }
16
+
17
+ return (
18
+ message.includes("suppression comment has no effect") || message.includes("unused suppression")
19
+ );
20
+ }
21
+
22
+ /**
23
+ * Split diagnostics into regular inline diagnostics and stale suppression cleanup.
24
+ *
25
+ * Stale suppression warnings stay visible even when regular inline diagnostics are
26
+ * configured to show errors only.
27
+ */
28
+ export function splitSuppressionDiagnostics(
29
+ diagnostics: Diagnostic[],
30
+ maxSeverity: number,
31
+ ): {
32
+ regular: Diagnostic[];
33
+ suppressions: Diagnostic[];
34
+ } {
35
+ const suppressionMaxSeverity = Math.max(maxSeverity, SUPPRESSION_WARNING_SEVERITY);
36
+ const regular: Diagnostic[] = [];
37
+ const suppressions: Diagnostic[] = [];
38
+
39
+ for (const diagnostic of diagnostics) {
40
+ const severity = diagnostic.severity;
41
+ if (severity === undefined) {
42
+ continue;
43
+ }
44
+
45
+ if (isStaleSuppressionDiagnostic(diagnostic)) {
46
+ if (severity <= suppressionMaxSeverity) {
47
+ suppressions.push(diagnostic);
48
+ }
49
+ continue;
50
+ }
51
+
52
+ if (severity <= maxSeverity) {
53
+ regular.push(diagnostic);
54
+ }
55
+ }
56
+
57
+ return { regular, suppressions };
58
+ }
@@ -0,0 +1,359 @@
1
+ // LSP result formatting — converts LSP response types into readable text.
2
+
3
+ import * as path from "node:path";
4
+ import type { GrepMatch } from "./search-fallback.ts";
5
+ import { isProjectSource } from "./summary.ts";
6
+ import type {
7
+ CodeAction,
8
+ DocumentSymbol,
9
+ Hover,
10
+ Location,
11
+ LocationLink,
12
+ MarkedString,
13
+ MarkupContent,
14
+ SymbolInformation,
15
+ WorkspaceEdit,
16
+ } from "./types.ts";
17
+ import { uriToFile } from "./utils.ts";
18
+
19
+ // ── Hover ─────────────────────────────────────────────────────────────
20
+
21
+ export function formatHover(hover: Hover): string {
22
+ const contents = hover.contents;
23
+
24
+ if (typeof contents === "string") return contents;
25
+ if ("value" in contents) {
26
+ const mc = contents as MarkupContent | { language: string; value: string };
27
+ if ("kind" in mc) return mc.value;
28
+ return `\`\`\`${mc.language}\n${mc.value}\n\`\`\``;
29
+ }
30
+ if (Array.isArray(contents)) {
31
+ return (contents as MarkedString[])
32
+ .map((c) => (typeof c === "string" ? c : `\`\`\`${c.language}\n${c.value}\n\`\`\``))
33
+ .join("\n\n");
34
+ }
35
+
36
+ return String(contents);
37
+ }
38
+
39
+ // ── Locations ─────────────────────────────────────────────────────────
40
+
41
+ function formatLocationLine(loc: Location, cwd: string): string {
42
+ const file = relPath(uriToFile(loc.uri), cwd);
43
+ const line = loc.range.start.line + 1;
44
+ const col = loc.range.start.character + 1;
45
+ return `${file}:${line}:${col}`;
46
+ }
47
+
48
+ function formatLocationList(label: string, locs: Location[], cwd: string): string {
49
+ const lines = [`${label} (${locs.length} locations):\n`];
50
+ for (const loc of locs) {
51
+ lines.push(`- ${formatLocationLine(loc, cwd)}`);
52
+ }
53
+ return lines.join("\n");
54
+ }
55
+
56
+ function formatExternalFallback(label: string, locs: Location[], cwd: string): string {
57
+ const maxShown = 3;
58
+ const shown = locs.slice(0, maxShown);
59
+ const hidden = locs.length - maxShown;
60
+
61
+ let result: string;
62
+ if (shown.length === 1) {
63
+ result = `${label}: ${formatLocationLine(shown[0], cwd)}`;
64
+ } else {
65
+ const lines = [`${label} (${locs.length} locations):\n`];
66
+ for (const loc of shown) {
67
+ lines.push(`- ${formatLocationLine(loc, cwd)}`);
68
+ }
69
+ result = lines.join("\n");
70
+ }
71
+
72
+ if (hidden > 0) {
73
+ result += `\n+${hidden} more external ${hidden === 1 ? "location" : "locations"}`;
74
+ }
75
+
76
+ return result;
77
+ }
78
+
79
+ function formatExternalSuffix(count: number): string {
80
+ return count === 1
81
+ ? "+1 external location (node_modules, .pnpm, or out-of-tree)"
82
+ : `+${count} external locations (node_modules, .pnpm, or out-of-tree)`;
83
+ }
84
+
85
+ export function formatLocations(label: string, locations: Location[], cwd: string): string {
86
+ const projectLocs: Location[] = [];
87
+ const externalLocs: Location[] = [];
88
+ for (const loc of locations) {
89
+ if (isProjectSource(uriToFile(loc.uri), cwd)) {
90
+ projectLocs.push(loc);
91
+ } else {
92
+ externalLocs.push(loc);
93
+ }
94
+ }
95
+
96
+ let result: string;
97
+
98
+ if (projectLocs.length === 1) {
99
+ result = `${label}: ${formatLocationLine(projectLocs[0], cwd)}`;
100
+ } else if (projectLocs.length > 1) {
101
+ result = formatLocationList(label, projectLocs, cwd);
102
+ } else if (externalLocs.length > 0) {
103
+ return formatExternalFallback(label, externalLocs, cwd);
104
+ } else {
105
+ return `${label}: No locations found.`;
106
+ }
107
+
108
+ if (externalLocs.length > 0) {
109
+ result += `\n${formatExternalSuffix(externalLocs.length)}`;
110
+ }
111
+
112
+ return result;
113
+ }
114
+
115
+ export function normalizeLocations(result: Location | Location[] | LocationLink[]): Location[] {
116
+ if (Array.isArray(result)) {
117
+ return result.map((item) => {
118
+ if ("targetUri" in item) {
119
+ const link = item as LocationLink;
120
+ return { uri: link.targetUri, range: link.targetSelectionRange };
121
+ }
122
+ return item as Location;
123
+ });
124
+ }
125
+ return [result as Location];
126
+ }
127
+
128
+ // ── Symbols ───────────────────────────────────────────────────────────
129
+
130
+ export function formatDocumentSymbols(symbols: DocumentSymbol[], indent: number): string {
131
+ const lines: string[] = [];
132
+ const prefix = " ".repeat(indent);
133
+
134
+ for (const sym of symbols) {
135
+ const kind = symbolKindName(sym.kind);
136
+ const line = sym.selectionRange.start.line + 1;
137
+ const detail = sym.detail ? ` — ${sym.detail}` : "";
138
+ lines.push(`${prefix}- ${kind} **${sym.name}**${detail} (line ${line})`);
139
+
140
+ if (sym.children && sym.children.length > 0) {
141
+ lines.push(formatDocumentSymbols(sym.children, indent + 1));
142
+ }
143
+ }
144
+
145
+ return lines.join("\n");
146
+ }
147
+
148
+ export function formatSymbolInformation(symbols: SymbolInformation[], cwd: string): string {
149
+ const projectSyms: SymbolInformation[] = [];
150
+ const externalSyms: SymbolInformation[] = [];
151
+ for (const sym of symbols) {
152
+ if (isProjectSource(uriToFile(sym.location.uri), cwd)) {
153
+ projectSyms.push(sym);
154
+ } else {
155
+ externalSyms.push(sym);
156
+ }
157
+ }
158
+
159
+ const lines: string[] = [];
160
+ const symbolsToShow = projectSyms.length > 0 ? projectSyms : externalSyms;
161
+ for (const sym of symbolsToShow) {
162
+ const kind = symbolKindName(sym.kind);
163
+ const file = relPath(uriToFile(sym.location.uri), cwd);
164
+ const line = sym.location.range.start.line + 1;
165
+ const container = sym.containerName ? ` (in ${sym.containerName})` : "";
166
+ lines.push(`- ${kind} **${sym.name}**${container} — ${file}:${line}`);
167
+ }
168
+
169
+ if (projectSyms.length > 0 && externalSyms.length > 0) {
170
+ const suffix =
171
+ externalSyms.length === 1
172
+ ? "+1 external symbol (node_modules, .pnpm, or out-of-tree)"
173
+ : `+${externalSyms.length} external symbols (node_modules, .pnpm, or out-of-tree)`;
174
+ lines.push(`- _${suffix}_`);
175
+ }
176
+
177
+ return lines.join("\n");
178
+ }
179
+
180
+ // ── Workspace Edits ───────────────────────────────────────────────────
181
+
182
+ interface EditEntry {
183
+ file: string;
184
+ edits: Array<{ range: { start: { line: number } }; newText: string }>;
185
+ }
186
+
187
+ function partitionWorkspaceEdit(
188
+ edit: WorkspaceEdit,
189
+ cwd: string,
190
+ ): { projectChanges: EditEntry[]; externalCount: number } {
191
+ const projectChanges: EditEntry[] = [];
192
+ let externalCount = 0;
193
+
194
+ if (edit.changes) {
195
+ for (const [uri, edits] of Object.entries(edit.changes)) {
196
+ const filePath = uriToFile(uri);
197
+ if (isProjectSource(filePath, cwd)) {
198
+ projectChanges.push({ file: relPath(filePath, cwd), edits });
199
+ } else {
200
+ externalCount++;
201
+ }
202
+ }
203
+ }
204
+
205
+ if (edit.documentChanges) {
206
+ for (const dc of edit.documentChanges) {
207
+ const filePath = uriToFile(dc.textDocument.uri);
208
+ if (isProjectSource(filePath, cwd)) {
209
+ projectChanges.push({ file: relPath(filePath, cwd), edits: dc.edits });
210
+ } else {
211
+ externalCount++;
212
+ }
213
+ }
214
+ }
215
+
216
+ return { projectChanges, externalCount };
217
+ }
218
+
219
+ export function formatWorkspaceEdit(edit: WorkspaceEdit, cwd: string): string {
220
+ const { projectChanges, externalCount } = partitionWorkspaceEdit(edit, cwd);
221
+
222
+ const lines: string[] = ["Rename workspace edit:\n"];
223
+ for (const { file, edits } of projectChanges) {
224
+ lines.push(`**${file}** (${edits.length} change(s))`);
225
+ for (const e of edits) {
226
+ const line = e.range.start.line + 1;
227
+ lines.push(` Line ${line}: → "${e.newText}"`);
228
+ }
229
+ }
230
+
231
+ if (externalCount > 0) {
232
+ const suffix =
233
+ externalCount === 1
234
+ ? "+1 external file (node_modules, .pnpm, or out-of-tree)"
235
+ : `+${externalCount} external files (node_modules, .pnpm, or out-of-tree)`;
236
+ lines.push(`\n_${suffix}_`);
237
+ }
238
+
239
+ return lines.join("\n");
240
+ }
241
+
242
+ // ── Code Actions ──────────────────────────────────────────────────────
243
+
244
+ export function formatCodeActions(actions: CodeAction[]): string {
245
+ const lines = [`Available code actions (${actions.length}):\n`];
246
+ for (const action of actions) {
247
+ const kind = action.kind ? ` [${action.kind}]` : "";
248
+ const preferred = action.isPreferred ? " ⭐" : "";
249
+ lines.push(`- **${action.title}**${kind}${preferred}`);
250
+ if (action.edit) {
251
+ const fileCount = action.edit.changes
252
+ ? Object.keys(action.edit.changes).length
253
+ : (action.edit.documentChanges?.length ?? 0);
254
+ lines.push(` Edits ${fileCount} file(s)`);
255
+ }
256
+ }
257
+ return lines.join("\n");
258
+ }
259
+
260
+ // ── Workspace Symbols ─────────────────────────────────────────────────
261
+
262
+ export function formatWorkspaceSymbols(symbols: SymbolInformation[], cwd: string): string {
263
+ if (symbols.length === 0) return "No symbols found.";
264
+
265
+ const projectSyms: SymbolInformation[] = [];
266
+ const externalSyms: SymbolInformation[] = [];
267
+ for (const sym of symbols) {
268
+ if (isProjectSource(uriToFile(sym.location.uri), cwd)) {
269
+ projectSyms.push(sym);
270
+ } else {
271
+ externalSyms.push(sym);
272
+ }
273
+ }
274
+
275
+ if (projectSyms.length === 0 && externalSyms.length > 0) {
276
+ return `Workspace symbols: No in-project symbols found.\n+${externalSyms.length} external symbol${externalSyms.length === 1 ? "" : "s"} (node_modules, .pnpm, or out-of-tree).`;
277
+ }
278
+
279
+ const lines = [`Workspace symbols (${projectSyms.length}):\n`];
280
+ for (const sym of projectSyms) {
281
+ const kind = symbolKindName(sym.kind);
282
+ const file = relPath(uriToFile(sym.location.uri), cwd);
283
+ const line = sym.location.range.start.line + 1;
284
+ const col = sym.location.range.start.character + 1;
285
+ const container = sym.containerName ? ` — ${sym.containerName}` : "";
286
+ lines.push(`- **${sym.name}** (${kind})${container} — ${file}:${line}:${col}`);
287
+ }
288
+
289
+ if (externalSyms.length > 0) {
290
+ const suffix =
291
+ externalSyms.length === 1
292
+ ? "+1 external symbol (node_modules, .pnpm, or out-of-tree)"
293
+ : `+${externalSyms.length} external symbols (node_modules, .pnpm, or out-of-tree)`;
294
+ lines.push(`\n_${suffix}_`);
295
+ }
296
+
297
+ return lines.join("\n");
298
+ }
299
+
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
+ // ── Symbol Kind Names ─────────────────────────────────────────────────
321
+
322
+ const SYMBOL_KIND_NAMES: Record<number, string> = {
323
+ 1: "File",
324
+ 2: "Module",
325
+ 3: "Namespace",
326
+ 4: "Package",
327
+ 5: "Class",
328
+ 6: "Method",
329
+ 7: "Property",
330
+ 8: "Field",
331
+ 9: "Constructor",
332
+ 10: "Enum",
333
+ 11: "Interface",
334
+ 12: "Function",
335
+ 13: "Variable",
336
+ 14: "Constant",
337
+ 15: "String",
338
+ 16: "Number",
339
+ 17: "Boolean",
340
+ 18: "Array",
341
+ 19: "Object",
342
+ 20: "Key",
343
+ 21: "Null",
344
+ 22: "EnumMember",
345
+ 23: "Struct",
346
+ 24: "Event",
347
+ 25: "Operator",
348
+ 26: "TypeParameter",
349
+ };
350
+
351
+ function symbolKindName(kind: number): string {
352
+ return SYMBOL_KIND_NAMES[kind] ?? `Kind(${kind})`;
353
+ }
354
+
355
+ // ── Helpers ───────────────────────────────────────────────────────────
356
+
357
+ function relPath(filePath: string, cwd: string): string {
358
+ return path.relative(cwd, filePath);
359
+ }