@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,315 @@
1
+ // Export extraction for supported files.
2
+
3
+ import { nodeToRange } from "./coordinates.ts";
4
+ import type { TreeSitterRuntime } from "./runtime.ts";
5
+ import type { SyntaxNodeLike } from "./syntax-node.ts";
6
+ import type { ExportRecord, TreeSitterResult } from "./types.ts";
7
+
8
+ /** Extract export records from a supported file. */
9
+ export async function extractExports(
10
+ runtime: TreeSitterRuntime,
11
+ filePath: string,
12
+ ): Promise<TreeSitterResult<ExportRecord[]>> {
13
+ const parseResult = await runtime.parseFile(filePath);
14
+ if (parseResult.kind !== "success") return parseResult;
15
+
16
+ const { tree, source } = parseResult.data;
17
+ const exports: ExportRecord[] = [];
18
+
19
+ try {
20
+ collectTopLevelExports(tree.rootNode, source, exports);
21
+ return { kind: "success", data: exports };
22
+ } finally {
23
+ tree.delete();
24
+ }
25
+ }
26
+
27
+ /** Only collect file-level exports; nested namespace/module exports are not module exports. */
28
+ function collectTopLevelExports(
29
+ rootNode: SyntaxNodeLike,
30
+ source: string,
31
+ exports: ExportRecord[],
32
+ ): void {
33
+ for (const child of rootNode.children) {
34
+ if (child.type !== "export_statement") continue;
35
+ handleExportStatement(child, source, exports);
36
+ }
37
+ }
38
+
39
+ /** Dispatch a single export statement into declaration, re-export, default, or named export records. */
40
+ function handleExportStatement(
41
+ node: SyntaxNodeLike,
42
+ source: string,
43
+ exports: ExportRecord[],
44
+ ): void {
45
+ const decl = findDeclarationChild(node);
46
+
47
+ if (decl) {
48
+ extractDeclarationExport(decl, source, exports);
49
+ return;
50
+ }
51
+
52
+ const sourceNode = findStringChild(node);
53
+ if (sourceNode) {
54
+ extractReExport(node, sourceNode, source, exports);
55
+ return;
56
+ }
57
+
58
+ if (hasExportAssignment(node)) {
59
+ extractExportAssignment(node, source, exports);
60
+ return;
61
+ }
62
+
63
+ if (hasDefaultKeyword(node)) {
64
+ extractDefaultExport(node, source, exports);
65
+ return;
66
+ }
67
+
68
+ extractNamedExportClause(node, source, exports);
69
+ }
70
+
71
+ function findDeclarationChild(node: SyntaxNodeLike): SyntaxNodeLike | undefined {
72
+ return node.children.find((child) => DECLARATION_EXPORT_NODE_TYPES.has(child.type));
73
+ }
74
+
75
+ const DECLARATION_EXPORT_NODE_TYPES = new Set([
76
+ "function_declaration",
77
+ "generator_function_declaration",
78
+ "class_declaration",
79
+ "abstract_class_declaration",
80
+ "interface_declaration",
81
+ "type_alias_declaration",
82
+ "enum_declaration",
83
+ "lexical_declaration",
84
+ "ambient_declaration",
85
+ "internal_module",
86
+ "module",
87
+ ]);
88
+
89
+ function extractDeclarationExport(
90
+ decl: SyntaxNodeLike,
91
+ source: string,
92
+ exports: ExportRecord[],
93
+ ): void {
94
+ if (decl.type === "lexical_declaration") {
95
+ extractLexicalDeclarationExports(decl, source, exports);
96
+ return;
97
+ }
98
+
99
+ if (decl.type === "ambient_declaration") {
100
+ extractAmbientDeclarationExport(decl, source, exports);
101
+ return;
102
+ }
103
+
104
+ const name = getDeclarationName(decl);
105
+ if (!name) return;
106
+
107
+ exports.push({
108
+ name,
109
+ kind: exportKindForDeclaration(decl.type),
110
+ range: nodeToRange(decl, source),
111
+ });
112
+ }
113
+
114
+ function extractLexicalDeclarationExports(
115
+ decl: SyntaxNodeLike,
116
+ source: string,
117
+ exports: ExportRecord[],
118
+ ): void {
119
+ for (const child of decl.children) {
120
+ if (child.type !== "variable_declarator") continue;
121
+ const nameNode = findNameNode(child);
122
+ if (!nameNode) continue;
123
+ exports.push({
124
+ name: nameNode.text,
125
+ kind: "variable",
126
+ range: nodeToRange(child, source),
127
+ });
128
+ }
129
+ }
130
+
131
+ /** Extract the declared symbol from `export declare ...` statements. */
132
+ function extractAmbientDeclarationExport(
133
+ decl: SyntaxNodeLike,
134
+ source: string,
135
+ exports: ExportRecord[],
136
+ ): void {
137
+ const nested = decl.children.find((child) => child.type !== "declare" && child.type !== ";");
138
+ if (!nested) return;
139
+
140
+ const name = getDeclarationName(nested);
141
+ if (!name) return;
142
+
143
+ exports.push({
144
+ name,
145
+ kind: exportKindForDeclaration(nested.type),
146
+ range: nodeToRange(decl, source),
147
+ });
148
+ }
149
+
150
+ /** Map Tree-sitter declaration node types to stable public export kinds. */
151
+ function exportKindForDeclaration(type: string): string {
152
+ switch (type) {
153
+ case "function_declaration":
154
+ case "generator_function_declaration":
155
+ case "function_signature":
156
+ return "function";
157
+ case "class_declaration":
158
+ case "abstract_class_declaration":
159
+ return "class";
160
+ case "interface_declaration":
161
+ return "interface";
162
+ case "type_alias_declaration":
163
+ return "type";
164
+ case "enum_declaration":
165
+ return "enum";
166
+ case "internal_module":
167
+ case "module":
168
+ return "namespace";
169
+ default:
170
+ return type.replace(/_declaration$/, "");
171
+ }
172
+ }
173
+
174
+ function hasDefaultKeyword(node: SyntaxNodeLike): boolean {
175
+ return node.children.some((child) => child.type === "default");
176
+ }
177
+
178
+ function hasExportAssignment(node: SyntaxNodeLike): boolean {
179
+ return node.children.some((child) => child.type === "=");
180
+ }
181
+
182
+ function extractExportAssignment(
183
+ node: SyntaxNodeLike,
184
+ source: string,
185
+ exports: ExportRecord[],
186
+ ): void {
187
+ const expr = node.children.find(
188
+ (child) => child.type !== "export" && child.type !== "=" && child.type !== ";",
189
+ );
190
+
191
+ exports.push({
192
+ name: expr ? expr.text.substring(0, 60) : "=",
193
+ kind: "export assignment",
194
+ range: nodeToRange(node, source),
195
+ });
196
+ }
197
+
198
+ function extractDefaultExport(node: SyntaxNodeLike, source: string, exports: ExportRecord[]): void {
199
+ const expr = node.children.find(
200
+ (child) => child.type !== "export" && child.type !== "default" && child.type !== ";",
201
+ );
202
+ exports.push({
203
+ name: expr ? expr.text.substring(0, 60) : "default",
204
+ kind: "default export",
205
+ range: nodeToRange(node, source),
206
+ });
207
+ }
208
+
209
+ function findStringChild(node: SyntaxNodeLike): SyntaxNodeLike | undefined {
210
+ return node.children.find((child) => child.type === "string");
211
+ }
212
+
213
+ function extractReExport(
214
+ node: SyntaxNodeLike,
215
+ sourceNode: SyntaxNodeLike,
216
+ source: string,
217
+ exports: ExportRecord[],
218
+ ): void {
219
+ const specifier = sourceNode.text.replace(/^["']|["']$/g, "");
220
+ const namespaceExport = node.children.find((child) => child.type === "namespace_export");
221
+ if (namespaceExport) {
222
+ extractNamespaceReExport(namespaceExport, specifier, source, exports);
223
+ return;
224
+ }
225
+
226
+ const exportClause = node.children.find((child) => child.type === "export_clause");
227
+ if (exportClause) {
228
+ extractExportSpecifiers(exportClause, source, exports, {
229
+ kind: "re-export",
230
+ moduleSpecifier: specifier,
231
+ });
232
+ return;
233
+ }
234
+
235
+ exports.push({
236
+ name: "*",
237
+ kind: "re-export",
238
+ range: nodeToRange(node, source),
239
+ moduleSpecifier: specifier,
240
+ });
241
+ }
242
+
243
+ /** Extract `export * as name from "module"` namespace re-export records. */
244
+ function extractNamespaceReExport(
245
+ node: SyntaxNodeLike,
246
+ moduleSpecifier: string,
247
+ source: string,
248
+ exports: ExportRecord[],
249
+ ): void {
250
+ const nameNode = findNameNode(node);
251
+ if (!nameNode) return;
252
+
253
+ exports.push({
254
+ name: nameNode.text,
255
+ kind: "re-export",
256
+ range: nodeToRange(node, source),
257
+ moduleSpecifier,
258
+ });
259
+ }
260
+
261
+ function extractNamedExportClause(
262
+ node: SyntaxNodeLike,
263
+ source: string,
264
+ exports: ExportRecord[],
265
+ ): void {
266
+ const exportClause = node.children.find((child) => child.type === "export_clause");
267
+ if (!exportClause) return;
268
+
269
+ extractExportSpecifiers(exportClause, source, exports, { kind: "export" });
270
+ }
271
+
272
+ function extractExportSpecifiers(
273
+ exportClause: SyntaxNodeLike,
274
+ source: string,
275
+ exports: ExportRecord[],
276
+ options: { kind: "export" | "re-export"; moduleSpecifier?: string },
277
+ ): void {
278
+ for (const child of exportClause.children) {
279
+ if (child.type !== "export_specifier") continue;
280
+ const exportedName = child.childForFieldName("alias") ?? child.childForFieldName("name");
281
+ if (!exportedName) continue;
282
+ exports.push({
283
+ name: exportedName.text,
284
+ kind: options.kind,
285
+ range: nodeToRange(child, source),
286
+ ...(options.moduleSpecifier ? { moduleSpecifier: options.moduleSpecifier } : {}),
287
+ });
288
+ }
289
+ }
290
+
291
+ function findNameNode(node: SyntaxNodeLike): SyntaxNodeLike | null {
292
+ return (
293
+ node.childForFieldName("name") ??
294
+ node.children.find((child) =>
295
+ [
296
+ "identifier",
297
+ "type_identifier",
298
+ "property_identifier",
299
+ "private_property_identifier",
300
+ ].includes(child.type),
301
+ ) ??
302
+ null
303
+ );
304
+ }
305
+
306
+ function getDeclarationName(node: SyntaxNodeLike): string | null {
307
+ const nameNode =
308
+ node.childForFieldName("name") ?? node.children.find((child) => child.type === "string");
309
+ if (!nameNode) return null;
310
+ return nameNode.type === "string" ? stripQuotes(nameNode.text) : nameNode.text;
311
+ }
312
+
313
+ function stripQuotes(text: string): string {
314
+ return text.replace(/^["']|["']$/g, "");
315
+ }
@@ -0,0 +1,104 @@
1
+ // Formatting helpers for tree_sitter tool output.
2
+
3
+ import type { OutlineItem, TreeSitterResult } from "./types.ts";
4
+
5
+ export const MAX_ITEMS = 100;
6
+
7
+ export function validationError(message: string): string {
8
+ return `**Validation error:** ${message}`;
9
+ }
10
+
11
+ export function formatNonSuccess(
12
+ result: Exclude<TreeSitterResult<unknown>, { kind: "success" }>,
13
+ ): string {
14
+ switch (result.kind) {
15
+ case "unsupported-language":
16
+ return `**Unsupported language:** ${result.message}`;
17
+ case "file-access-error":
18
+ return `**File access error:** ${result.message}`;
19
+ case "validation-error":
20
+ return `**Validation error:** ${result.message}`;
21
+ case "runtime-error":
22
+ return `**Runtime error:** ${result.message}`;
23
+ }
24
+ }
25
+
26
+ export function truncateText(text: string, maxLen: number): string {
27
+ if (text.length <= maxLen) return text;
28
+ return `${text.substring(0, maxLen)}…`;
29
+ }
30
+
31
+ export function truncate<T>(items: T[], max: number): { included: T[]; truncated: number } {
32
+ if (items.length <= max) return { included: items, truncated: 0 };
33
+ return { included: items.slice(0, max), truncated: items.length - max };
34
+ }
35
+
36
+ export function truncatedNotice(count: number, kind: string, max = MAX_ITEMS): string {
37
+ return `⚠ ${count} additional ${kind} omitted (result capped at ${max}).`;
38
+ }
39
+
40
+ interface OutlineFormatContext {
41
+ lines: string[];
42
+ max: number;
43
+ emitted: number;
44
+ omitted: number;
45
+ }
46
+
47
+ /**
48
+ * Append outline items to `lines` while enforcing a recursive item cap.
49
+ *
50
+ * The cap counts every printed declaration, including nested methods and other
51
+ * children. This keeps generated or deeply nested files from bypassing the
52
+ * `tree_sitter` tool's agent-facing result limit.
53
+ */
54
+ export function formatOutlineItemsCapped(
55
+ items: OutlineItem[],
56
+ lines: string[],
57
+ max = MAX_ITEMS,
58
+ ): { emitted: number; omitted: number } {
59
+ const context: OutlineFormatContext = { lines, max, emitted: 0, omitted: 0 };
60
+ appendOutlineItems(items, context, 0);
61
+ return { emitted: context.emitted, omitted: context.omitted };
62
+ }
63
+
64
+ /** Append outline items without a cap. Prefer `formatOutlineItemsCapped` for tool output. */
65
+ export function formatOutlineItems(items: OutlineItem[], lines: string[], depth: number): void {
66
+ appendOutlineItems(
67
+ items,
68
+ { lines, max: Number.POSITIVE_INFINITY, emitted: 0, omitted: 0 },
69
+ depth,
70
+ );
71
+ }
72
+
73
+ function appendOutlineItems(
74
+ items: OutlineItem[],
75
+ context: OutlineFormatContext,
76
+ depth: number,
77
+ ): void {
78
+ for (const item of items) {
79
+ if (context.emitted >= context.max) {
80
+ context.omitted += countOutlineItems(item);
81
+ continue;
82
+ }
83
+
84
+ appendOutlineItem(item, context, depth);
85
+ context.emitted++;
86
+
87
+ if (item.children?.length) {
88
+ appendOutlineItems(item.children, context, depth + 1);
89
+ }
90
+ }
91
+ }
92
+
93
+ function appendOutlineItem(item: OutlineItem, context: OutlineFormatContext, depth: number): void {
94
+ const indent = " ".repeat(depth);
95
+ const range = item.range;
96
+ context.lines.push(
97
+ `${indent}- ${item.kind}: ${item.name} (L${range.startLine}:${range.startCharacter}-L${range.endLine}:${range.endCharacter})`,
98
+ );
99
+ }
100
+
101
+ function countOutlineItems(item: OutlineItem): number {
102
+ const childCount = item.children?.reduce((sum, child) => sum + countOutlineItems(child), 0) ?? 0;
103
+ return 1 + childCount;
104
+ }
@@ -0,0 +1,42 @@
1
+ // Import extraction for supported files.
2
+
3
+ import { nodeToRange } from "./coordinates.ts";
4
+ import type { TreeSitterRuntime } from "./runtime.ts";
5
+ import type { SyntaxNodeLike } from "./syntax-node.ts";
6
+ import type { ImportRecord, TreeSitterResult } from "./types.ts";
7
+
8
+ /** Extract import records from a supported file. */
9
+ export async function extractImports(
10
+ runtime: TreeSitterRuntime,
11
+ filePath: string,
12
+ ): Promise<TreeSitterResult<ImportRecord[]>> {
13
+ const parseResult = await runtime.parseFile(filePath);
14
+ if (parseResult.kind !== "success") return parseResult;
15
+
16
+ const { tree, source } = parseResult.data;
17
+ const imports: ImportRecord[] = [];
18
+
19
+ try {
20
+ walkForImports(tree.rootNode, source, imports);
21
+ return { kind: "success", data: imports };
22
+ } finally {
23
+ tree.delete();
24
+ }
25
+ }
26
+
27
+ function walkForImports(node: SyntaxNodeLike, source: string, imports: ImportRecord[]): void {
28
+ if (node.type === "import_statement") {
29
+ const sourceNode = node.childForFieldName("source");
30
+ if (sourceNode) {
31
+ const specifier = sourceNode.text.replace(/^["']|["']$/g, "");
32
+ imports.push({
33
+ moduleSpecifier: specifier,
34
+ range: nodeToRange(node, source),
35
+ });
36
+ }
37
+ return;
38
+ }
39
+ for (const child of node.children) {
40
+ walkForImports(child, source, imports);
41
+ }
42
+ }
@@ -0,0 +1,16 @@
1
+ // Public session factory and re-exports for @mrclrchtr/supi-tree-sitter.
2
+
3
+ export { createTreeSitterSession } from "./session.ts";
4
+ export type {
5
+ CalleesAtResult,
6
+ ExportRecord,
7
+ GrammarId,
8
+ ImportRecord,
9
+ NodeAtResult,
10
+ OutlineItem,
11
+ QueryCapture,
12
+ SourceRange,
13
+ SupportedExtension,
14
+ TreeSitterResult,
15
+ TreeSitterSession,
16
+ } from "./types.ts";
@@ -0,0 +1,116 @@
1
+ // Language detection and grammar resolution for Tree-sitter.
2
+ //
3
+ // All grammar WASM files are vendored in resources/grammars/<id>/ and
4
+ // shipped with the package. No runtime require.resolve() to third-party
5
+ // npm packages is needed.
6
+
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import type { GrammarId, SupportedExtension } from "./types.ts";
11
+
12
+ const sourceDir = path.dirname(fileURLToPath(import.meta.url));
13
+
14
+ /** Mapping from file extension to grammar identifier. */
15
+ const EXTENSION_GRAMMAR: Record<string, GrammarId> = {
16
+ ".js": "javascript",
17
+ ".jsx": "javascript",
18
+ ".mjs": "javascript",
19
+ ".cjs": "javascript",
20
+ ".ts": "typescript",
21
+ ".mts": "typescript",
22
+ ".cts": "typescript",
23
+ ".tsx": "tsx",
24
+ ".py": "python",
25
+ ".pyi": "python",
26
+ ".rs": "rust",
27
+ ".go": "go",
28
+ ".mod": "go",
29
+ ".c": "c",
30
+ ".h": "c",
31
+ ".cpp": "cpp",
32
+ ".hpp": "cpp",
33
+ ".cc": "cpp",
34
+ ".cxx": "cpp",
35
+ ".hxx": "cpp",
36
+ ".c++": "cpp",
37
+ ".h++": "cpp",
38
+ ".java": "java",
39
+ ".kt": "kotlin",
40
+ ".kts": "kotlin",
41
+ ".rb": "ruby",
42
+ ".sh": "bash",
43
+ ".bash": "bash",
44
+ ".zsh": "bash",
45
+ ".html": "html",
46
+ ".htm": "html",
47
+ ".xhtml": "html",
48
+ ".r": "r",
49
+ ".sql": "sql",
50
+ };
51
+
52
+ const SUPPORTED_EXTENSIONS = new Set<string>(Object.keys(EXTENSION_GRAMMAR));
53
+
54
+ /** Check if a file extension is supported. */
55
+ export function isSupportedFile(filePath: string): boolean {
56
+ return detectGrammar(filePath) !== undefined;
57
+ }
58
+
59
+ /** Get the file extension if it's supported, otherwise undefined. */
60
+ export function getSupportedExtension(filePath: string): SupportedExtension | undefined {
61
+ const ext = path.extname(filePath).toLowerCase();
62
+ return SUPPORTED_EXTENSIONS.has(ext) ? (ext as SupportedExtension) : undefined;
63
+ }
64
+
65
+ /** Detect the grammar for a file. Returns undefined if unsupported. */
66
+ export function detectGrammar(filePath: string): GrammarId | undefined {
67
+ const ext = path.extname(filePath).toLowerCase();
68
+ return EXTENSION_GRAMMAR[ext];
69
+ }
70
+
71
+ const JS_TS_GRAMMARS: ReadonlySet<GrammarId> = new Set(["javascript", "typescript", "tsx"]);
72
+
73
+ /** Returns true if the grammar is one the JS/TS extractors understand. */
74
+ export function isJsTsGrammar(grammarId: GrammarId): boolean {
75
+ return JS_TS_GRAMMARS.has(grammarId);
76
+ }
77
+
78
+ /** Grammar WASM file names within the vendored resources directory. */
79
+ const GRAMMAR_WASM: Record<GrammarId, string> = {
80
+ javascript: "tree-sitter-javascript.wasm",
81
+ typescript: "tree-sitter-typescript.wasm",
82
+ tsx: "tree-sitter-tsx.wasm",
83
+ python: "tree-sitter-python.wasm",
84
+ rust: "tree-sitter-rust.wasm",
85
+ go: "tree-sitter-go.wasm",
86
+ c: "tree-sitter-c.wasm",
87
+ cpp: "tree-sitter-cpp.wasm",
88
+ java: "tree-sitter-java.wasm",
89
+ kotlin: "tree-sitter-kotlin.wasm",
90
+ ruby: "tree-sitter-ruby.wasm",
91
+ bash: "tree-sitter-bash.wasm",
92
+ html: "tree-sitter-html.wasm",
93
+ r: "tree-sitter-r.wasm",
94
+ sql: "tree-sitter-sql.wasm",
95
+ };
96
+
97
+ const resourcesDir = path.resolve(sourceDir, "../resources/grammars");
98
+
99
+ /**
100
+ * Resolve the WASM grammar file path for a given grammar ID.
101
+ * All grammars use vendored WASM files under `resources/grammars/<id>/`.
102
+ */
103
+ export function resolveGrammarWasmPath(grammarId: GrammarId): string {
104
+ const wasmFile = GRAMMAR_WASM[grammarId];
105
+ const vendoredPath = path.join(resourcesDir, grammarId, wasmFile);
106
+
107
+ // Verify the vendored file exists at init time — fail fast if missing
108
+ if (!fs.existsSync(vendoredPath)) {
109
+ throw new Error(
110
+ `Vendored WASM grammar not found for "${grammarId}": expected at ${vendoredPath}. ` +
111
+ "Run `node scripts/vendor-wasm.mjs` or reinstall the package.",
112
+ );
113
+ }
114
+
115
+ return vendoredPath;
116
+ }
@@ -0,0 +1,96 @@
1
+ // Node-at-position lookup.
2
+
3
+ import { nodeToRange, publicToTreeSitter, splitSourceLines } from "./coordinates.ts";
4
+ import type { TreeSitterRuntime } from "./runtime.ts";
5
+ import type { NodeAtResult, SourceRange, TreeSitterResult } from "./types.ts";
6
+
7
+ const MAX_ANCESTRY = 10;
8
+
9
+ /** Find the smallest relevant node at a given 1-based position. */
10
+ export async function lookupNodeAt(
11
+ runtime: TreeSitterRuntime,
12
+ filePath: string,
13
+ line: number,
14
+ character: number,
15
+ ): Promise<TreeSitterResult<NodeAtResult>> {
16
+ if (!Number.isInteger(line) || line < 1) {
17
+ return { kind: "validation-error", message: "line must be a positive 1-based integer" };
18
+ }
19
+ if (!Number.isInteger(character) || character < 1) {
20
+ return { kind: "validation-error", message: "character must be a positive 1-based integer" };
21
+ }
22
+
23
+ const parseResult = await runtime.parseFile(filePath);
24
+ if (parseResult.kind !== "success") return parseResult;
25
+
26
+ const { tree, source } = parseResult.data;
27
+
28
+ try {
29
+ const boundsError = validateBounds(line, character, source);
30
+ if (boundsError) return boundsError;
31
+
32
+ const tsPoint = publicToTreeSitter(line, character, source);
33
+ const node = tree.rootNode.descendantForPosition(tsPoint);
34
+
35
+ if (!node) {
36
+ return { kind: "runtime-error", message: "No node found at the given position" };
37
+ }
38
+
39
+ return {
40
+ kind: "success",
41
+ data: {
42
+ type: node.type,
43
+ range: nodeToRange(node, source),
44
+ text: node.text,
45
+ ancestry: collectAncestry(node, source),
46
+ },
47
+ };
48
+ } finally {
49
+ tree.delete();
50
+ }
51
+ }
52
+
53
+ /** Validate that a requested public position exists in the source text. */
54
+ function validateBounds(
55
+ line: number,
56
+ character: number,
57
+ source: string,
58
+ ): TreeSitterResult<NodeAtResult> | null {
59
+ const lines = splitSourceLines(source);
60
+ if (line > lines.length) {
61
+ return { kind: "validation-error", message: "line is beyond end of file" };
62
+ }
63
+
64
+ const lineText = lines[line - 1] ?? "";
65
+ if (character > lineText.length + 1) {
66
+ return { kind: "validation-error", message: "character is beyond end of line" };
67
+ }
68
+
69
+ return null;
70
+ }
71
+
72
+ function collectAncestry(
73
+ node: {
74
+ type: string;
75
+ parent: {
76
+ type: string;
77
+ startPosition: { row: number; column: number };
78
+ endPosition: { row: number; column: number };
79
+ parent: unknown;
80
+ } | null;
81
+ },
82
+ source: string,
83
+ ): Array<{ type: string; range: SourceRange }> {
84
+ const ancestry: Array<{ type: string; range: SourceRange }> = [];
85
+ let parent = node.parent;
86
+ let count = 0;
87
+ while (parent && count < MAX_ANCESTRY) {
88
+ ancestry.push({
89
+ type: parent.type,
90
+ range: nodeToRange(parent, source),
91
+ });
92
+ parent = parent.parent as typeof parent | null;
93
+ count++;
94
+ }
95
+ return ancestry;
96
+ }