@mrclrchtr/supi-code-intelligence 4.1.0 → 4.3.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 (47) hide show
  1. package/README.md +4 -2
  2. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  4. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  5. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  6. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  7. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  8. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  9. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +2 -2
  10. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +5 -5
  11. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  12. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  13. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  15. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +2 -1
  16. package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +19 -0
  17. package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +1 -0
  18. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-c-family.ts +230 -0
  19. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +186 -0
  20. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-jvm.ts +311 -0
  21. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-polyglot.ts +218 -0
  22. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-scripting.ts +307 -0
  23. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +9 -5
  24. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +6 -2
  25. package/package.json +5 -5
  26. package/src/analysis/search/pattern.ts +2 -2
  27. package/src/analysis/target/symbol.ts +5 -2
  28. package/src/extension.ts +5 -1
  29. package/src/session/orientation/collect.ts +134 -180
  30. package/src/session/orientation/context-sections.ts +14 -48
  31. package/src/session/orientation-types.ts +14 -4
  32. package/src/session/orientation-workflow.ts +20 -48
  33. package/src/substrate/lsp/lifecycle.ts +55 -3
  34. package/src/substrate/lsp/state.ts +6 -0
  35. package/src/tool/graph/execute.ts +1 -1
  36. package/src/tool/graph/markdown.ts +222 -0
  37. package/src/tool/orientation/markdown.ts +66 -11
  38. package/src/tool/register.ts +10 -0
  39. package/src/ui/footer.ts +33 -8
  40. package/src/ui/markdown/overview-data.ts +3 -15
  41. package/src/ui/markdown/overview.ts +4 -10
  42. package/src/ui/markdown/types.ts +0 -1
  43. package/src/tool/graph/calls-md.ts +0 -41
  44. package/src/tool/graph/implementations-md.ts +0 -57
  45. package/src/tool/graph/markdown-base.ts +0 -88
  46. package/src/tool/graph/provider-location-md.ts +0 -9
  47. package/src/tool/graph/references-md.ts +0 -41
@@ -0,0 +1,218 @@
1
+ import { nodeToRange } from "../coordinates.ts";
2
+ import type { SyntaxNodeLike } from "../syntax-node.ts";
3
+ import type { OutlineItem } from "../types.ts";
4
+ import { extractCFamilyOutlineItems } from "./outline-c-family.ts";
5
+ import { extractHtmlSqlOutlineItems } from "./outline-html-sql.ts";
6
+ import { extractJvmOutlineItems } from "./outline-jvm.ts";
7
+ import { extractScriptingOutlineItems } from "./outline-scripting.ts";
8
+
9
+ /** Extract declarations whose Tree-sitter node shapes are specific to non-JS/TS grammars. */
10
+ export function extractPolyglotOutlineItems(
11
+ node: SyntaxNodeLike,
12
+ source: string,
13
+ ): OutlineItem[] | undefined {
14
+ const scriptingItems = extractScriptingOutlineItems(node, source);
15
+ if (scriptingItems) return scriptingItems;
16
+
17
+ const htmlSqlItems = extractHtmlSqlOutlineItems(node, source);
18
+ if (htmlSqlItems) return htmlSqlItems;
19
+
20
+ switch (node.type) {
21
+ case "function_definition": {
22
+ const name = node.childForFieldName("name");
23
+ return name
24
+ ? [{ name: name.text, kind: "function", range: nodeToRange(node, source) }]
25
+ : extractCFamilyOutlineItems(node, source);
26
+ }
27
+ case "class_definition":
28
+ return one(container(node, "class", source, pythonMethods));
29
+ case "expression_statement":
30
+ return pythonAssignments(node, source);
31
+ case "function_item":
32
+ return one(named(node, "function", source));
33
+ case "struct_item":
34
+ return one(container(node, "struct", source, rustFields));
35
+ case "union_item":
36
+ return one(container(node, "union", source, rustFields));
37
+ case "enum_item":
38
+ return one(container(node, "enum", source, rustEnumVariants));
39
+ case "trait_item":
40
+ return one(container(node, "interface", source, rustMethods));
41
+ case "impl_item":
42
+ return one(rustImplementation(node, source));
43
+ case "const_item":
44
+ return one(named(node, "constant", source));
45
+ case "static_item":
46
+ return one(named(node, "variable", source));
47
+ case "type_item":
48
+ return one(named(node, "type", source));
49
+ case "mod_item":
50
+ return one(named(node, "module", source));
51
+ case "method_declaration":
52
+ return one(named(node, "method", source));
53
+ case "type_spec":
54
+ return one(goType(node, source));
55
+ case "type_alias":
56
+ return one(named(node, "type", source));
57
+ case "const_spec":
58
+ return namedItems(node, "constant", source);
59
+ case "var_spec":
60
+ return namedItems(node, "variable", source);
61
+ default:
62
+ return extractCFamilyOutlineItems(node, source) ?? extractJvmOutlineItems(node, source);
63
+ }
64
+ }
65
+
66
+ function one(item: OutlineItem | null): OutlineItem[] {
67
+ return item ? [item] : [];
68
+ }
69
+
70
+ function named(node: SyntaxNodeLike, kind: string, source: string): OutlineItem | null {
71
+ const name = findName(node);
72
+ return name ? { name: name.text, kind, range: nodeToRange(node, source) } : null;
73
+ }
74
+
75
+ function container(
76
+ node: SyntaxNodeLike,
77
+ kind: string,
78
+ source: string,
79
+ collectChildren: (node: SyntaxNodeLike, source: string) => OutlineItem[],
80
+ ): OutlineItem | null {
81
+ const name = findName(node);
82
+ if (!name) return null;
83
+ return {
84
+ name: name.text,
85
+ kind,
86
+ range: nodeToRange(node, source),
87
+ children: collectChildren(node, source),
88
+ };
89
+ }
90
+
91
+ function pythonAssignments(node: SyntaxNodeLike, source: string): OutlineItem[] | undefined {
92
+ const assignment = node.children.find((child) => child.type === "assignment");
93
+ return assignment ? collectPythonAssignment(assignment, source) : undefined;
94
+ }
95
+
96
+ function collectPythonAssignment(node: SyntaxNodeLike, source: string): OutlineItem[] {
97
+ const items = pythonBindingItems(node.childForFieldName("left"), node, source);
98
+ const right = node.childForFieldName("right");
99
+ if (right?.type === "assignment") items.push(...collectPythonAssignment(right, source));
100
+ return items;
101
+ }
102
+
103
+ function pythonBindingItems(
104
+ target: SyntaxNodeLike | null,
105
+ declaration: SyntaxNodeLike,
106
+ source: string,
107
+ ): OutlineItem[] {
108
+ if (!target) return [];
109
+ if (target.type === "identifier") {
110
+ return [{ name: target.text, kind: "variable", range: nodeToRange(declaration, source) }];
111
+ }
112
+ if (!PYTHON_BINDING_PATTERNS.has(target.type)) return [];
113
+ return target.children.flatMap((child) => pythonBindingItems(child, declaration, source));
114
+ }
115
+
116
+ const PYTHON_BINDING_PATTERNS = new Set(["pattern_list", "list_pattern", "tuple_pattern"]);
117
+
118
+ function pythonMethods(node: SyntaxNodeLike, source: string): OutlineItem[] {
119
+ const body = node.childForFieldName("body");
120
+ if (!body) return [];
121
+ return body.children.flatMap((child) => {
122
+ const declaration =
123
+ child.type === "decorated_definition"
124
+ ? child.children.find((nested) => nested.type === "function_definition")
125
+ : child;
126
+ if (declaration?.type !== "function_definition") return [];
127
+ const item = named(declaration, "method", source);
128
+ return item ? [item] : [];
129
+ });
130
+ }
131
+
132
+ function rustFields(node: SyntaxNodeLike, source: string): OutlineItem[] {
133
+ const body = node.childForFieldName("body");
134
+ return collectNamedChildren(body, new Set(["field_declaration"]), "field", source);
135
+ }
136
+
137
+ function rustEnumVariants(node: SyntaxNodeLike, source: string): OutlineItem[] {
138
+ const body = node.childForFieldName("body");
139
+ return collectNamedChildren(body, new Set(["enum_variant"]), "enum-member", source);
140
+ }
141
+
142
+ function rustMethods(node: SyntaxNodeLike, source: string): OutlineItem[] {
143
+ const body = node.childForFieldName("body");
144
+ return collectNamedChildren(
145
+ body,
146
+ new Set(["function_item", "function_signature_item"]),
147
+ "method",
148
+ source,
149
+ );
150
+ }
151
+
152
+ function rustImplementation(node: SyntaxNodeLike, source: string): OutlineItem | null {
153
+ const type = node.childForFieldName("type");
154
+ if (!type) return null;
155
+ const trait = node.childForFieldName("trait");
156
+ return {
157
+ name: trait ? `${trait.text} for ${type.text}` : type.text,
158
+ kind: "implementation",
159
+ range: nodeToRange(node, source),
160
+ children: rustMethods(node, source),
161
+ };
162
+ }
163
+
164
+ function goType(node: SyntaxNodeLike, source: string): OutlineItem | null {
165
+ const type = node.childForFieldName("type");
166
+ if (!type) return named(node, "type", source);
167
+ if (type.type === "struct_type") return container(node, "struct", source, goStructFields);
168
+ if (type.type === "interface_type") return container(node, "interface", source, goMethods);
169
+ return named(node, "type", source);
170
+ }
171
+
172
+ function goStructFields(node: SyntaxNodeLike, source: string): OutlineItem[] {
173
+ const body = node
174
+ .childForFieldName("type")
175
+ ?.children.find((child) => child.type.endsWith("list"));
176
+ if (!body) return [];
177
+ return body.children.flatMap((child) =>
178
+ child.type === "field_declaration" ? namedItems(child, "field", source) : [],
179
+ );
180
+ }
181
+
182
+ function goMethods(node: SyntaxNodeLike, source: string): OutlineItem[] {
183
+ const body = node.childForFieldName("type");
184
+ return collectNamedChildren(body, new Set(["method_elem"]), "method", source);
185
+ }
186
+
187
+ function collectNamedChildren(
188
+ parent: SyntaxNodeLike | null | undefined,
189
+ accepted: ReadonlySet<string>,
190
+ kind: string,
191
+ source: string,
192
+ ): OutlineItem[] {
193
+ if (!parent) return [];
194
+ return parent.children.flatMap((child) => {
195
+ if (!accepted.has(child.type)) return [];
196
+ const item = named(child, kind, source);
197
+ return item ? [item] : [];
198
+ });
199
+ }
200
+
201
+ function namedItems(node: SyntaxNodeLike, kind: string, source: string): OutlineItem[] {
202
+ const names = node
203
+ .childrenForFieldName("name")
204
+ .filter((child) => NAME_NODE_TYPES.has(child.type));
205
+ if (names.length === 0) return one(named(node, kind, source));
206
+ const range = nodeToRange(node, source);
207
+ return names.map((name) => ({ name: name.text, kind, range }));
208
+ }
209
+
210
+ const NAME_NODE_TYPES = new Set(["identifier", "type_identifier", "field_identifier"]);
211
+
212
+ function findName(node: SyntaxNodeLike): SyntaxNodeLike | null {
213
+ return (
214
+ node.childForFieldName("name") ??
215
+ node.children.find((child) => NAME_NODE_TYPES.has(child.type)) ??
216
+ null
217
+ );
218
+ }
@@ -0,0 +1,307 @@
1
+ import { nodeToRange } from "../coordinates.ts";
2
+ import type { SyntaxNodeLike } from "../syntax-node.ts";
3
+ import type { OutlineItem } from "../types.ts";
4
+
5
+ /** Extract Ruby, shell, and R declarations from their grammar-specific node shapes. */
6
+ export function extractScriptingOutlineItems(
7
+ node: SyntaxNodeLike,
8
+ source: string,
9
+ ): OutlineItem[] | undefined {
10
+ const ruby = rubyItems(node, "top-level", source);
11
+ if (ruby) return ruby;
12
+
13
+ switch (node.type) {
14
+ case "variable_assignment":
15
+ return isNestedShellDeclaration(node) || node.parent?.type === "command"
16
+ ? []
17
+ : one(shellVariable(node, "variable", source));
18
+ case "declaration_command":
19
+ return isNestedShellDeclaration(node) ? [] : shellDeclarations(node, source);
20
+ case "function_definition": {
21
+ const nameType = node.childForFieldName("name")?.type;
22
+ if (nameType === "word") {
23
+ return isNestedShellDeclaration(node) ? [] : one(named(node, "function", source));
24
+ }
25
+ return nameType === "function" || nameType === "\\" ? [] : undefined;
26
+ }
27
+ case "binary_operator":
28
+ return rAssignments(node, source);
29
+ default:
30
+ return undefined;
31
+ }
32
+ }
33
+
34
+ function rubyItems(
35
+ node: SyntaxNodeLike,
36
+ context: "top-level" | "member",
37
+ source: string,
38
+ ): OutlineItem[] | undefined {
39
+ switch (node.type) {
40
+ case "assignment":
41
+ case "operator_assignment":
42
+ return rubyAssignments(node, context, source);
43
+ case "module":
44
+ return isRubyContainer(node) ? one(rubyContainer(node, "module", source)) : undefined;
45
+ case "class":
46
+ return isRubyContainer(node) ? one(rubyContainer(node, "class", source)) : undefined;
47
+ case "method":
48
+ return one(named(node, context === "member" ? "method" : "function", source));
49
+ case "singleton_method":
50
+ return one(named(node, "method", source));
51
+ case "singleton_class":
52
+ return rubyMembers(node.childForFieldName("body"), source);
53
+ case "alias":
54
+ return one(named(node, context === "member" ? "method" : "function", source));
55
+ case "call":
56
+ return context === "member" ? rubyCallMethods(node, source) : undefined;
57
+ case "block":
58
+ return node.childForFieldName("body")?.type === "block_body" ? [] : undefined;
59
+ case "do_block":
60
+ return node.childForFieldName("body")?.type === "body_statement" ? [] : undefined;
61
+ default:
62
+ return undefined;
63
+ }
64
+ }
65
+
66
+ function isRubyContainer(node: SyntaxNodeLike): boolean {
67
+ const name = node.childForFieldName("name");
68
+ const body = node.childForFieldName("body");
69
+ return Boolean(
70
+ name && RUBY_CONTAINER_NAME_TYPES.has(name.type) && (!body || body.type === "body_statement"),
71
+ );
72
+ }
73
+
74
+ function rubyContainer(node: SyntaxNodeLike, kind: string, source: string): OutlineItem | null {
75
+ const name = node.childForFieldName("name");
76
+ if (!name) return null;
77
+ return {
78
+ name: name.text,
79
+ kind,
80
+ range: nodeToRange(node, source),
81
+ children: rubyMembers(node.childForFieldName("body"), source),
82
+ };
83
+ }
84
+
85
+ function rubyMembers(body: SyntaxNodeLike | null | undefined, source: string): OutlineItem[] {
86
+ if (!body) return [];
87
+ return body.children.flatMap((child) => rubyItems(child, "member", source) ?? []);
88
+ }
89
+
90
+ function rubyAssignments(
91
+ node: SyntaxNodeLike,
92
+ context: "top-level" | "member",
93
+ source: string,
94
+ ): OutlineItem[] {
95
+ const left = node.childForFieldName("left");
96
+ if (!left) return [];
97
+ const range = nodeToRange(node, source);
98
+ const items = rubyBindingNodes(left)
99
+ .filter((binding) => context === "top-level" || binding.type !== "identifier")
100
+ .map((binding) => ({
101
+ name: binding.text,
102
+ kind:
103
+ binding.type === "constant" || binding.type === "scope_resolution"
104
+ ? "constant"
105
+ : "variable",
106
+ range,
107
+ }));
108
+ const right = unwrapParentheses(node.childForFieldName("right"));
109
+ const chained =
110
+ right?.type === "assignment" || right?.type === "operator_assignment"
111
+ ? rubyAssignments(right, context, source)
112
+ : [];
113
+ return [...items, ...chained];
114
+ }
115
+
116
+ function rubyBindingNodes(node: SyntaxNodeLike): SyntaxNodeLike[] {
117
+ if (RUBY_BINDING_TYPES.has(node.type)) return [node];
118
+ if (!RUBY_BINDING_PATTERNS.has(node.type)) return [];
119
+ return node.children.flatMap(rubyBindingNodes);
120
+ }
121
+
122
+ function rubyCallMethods(node: SyntaxNodeLike, source: string): OutlineItem[] | undefined {
123
+ const method = node.childForFieldName("method")?.text;
124
+ const argumentsNode = node.childForFieldName("arguments");
125
+ if (!method || !argumentsNode || node.childForFieldName("receiver")) return undefined;
126
+
127
+ if (RUBY_ATTRIBUTE_METHODS.has(method)) {
128
+ const range = nodeToRange(node, source);
129
+ return rubySymbolNames(argumentsNode).flatMap((name) => {
130
+ if (method === "attr_writer") return [{ name: `${name}=`, kind: "method", range }];
131
+ if (method === "attr_accessor") {
132
+ return [
133
+ { name, kind: "method", range },
134
+ { name: `${name}=`, kind: "method", range },
135
+ ];
136
+ }
137
+ return [{ name, kind: "method", range }];
138
+ });
139
+ }
140
+ if (method === "alias_method") {
141
+ const name = rubySymbolNames(argumentsNode)[0];
142
+ return name ? [{ name, kind: "method", range: nodeToRange(node, source) }] : [];
143
+ }
144
+ if (RUBY_VISIBILITY_METHODS.has(method)) {
145
+ const declaration = argumentsNode.children.find(
146
+ (child) => child.type === "method" || child.type === "singleton_method",
147
+ );
148
+ return declaration ? rubyItems(declaration, "member", source) : [];
149
+ }
150
+ return undefined;
151
+ }
152
+
153
+ function rubySymbolNames(node: SyntaxNodeLike): string[] {
154
+ return node.children
155
+ .filter((child) => child.type === "simple_symbol")
156
+ .map((child) => child.text.replace(/^:/, ""));
157
+ }
158
+
159
+ function isNestedShellDeclaration(node: SyntaxNodeLike): boolean {
160
+ if (isBackgroundedShellNode(node)) return true;
161
+ for (let parent = node.parent; parent; parent = parent.parent) {
162
+ if (SHELL_SCOPE_BOUNDARIES.has(parent.type)) return true;
163
+ }
164
+ return false;
165
+ }
166
+
167
+ function isBackgroundedShellNode(node: SyntaxNodeLike): boolean {
168
+ for (let current = node, parent = node.parent; parent; current = parent, parent = parent.parent) {
169
+ const index = parent.children.findIndex((child) => sameNode(child, current));
170
+ if (index >= 0 && parent.children[index + 1]?.type === "&") return true;
171
+ }
172
+ return false;
173
+ }
174
+
175
+ function sameNode(left: SyntaxNodeLike, right: SyntaxNodeLike): boolean {
176
+ return (
177
+ left.type === right.type &&
178
+ left.startPosition.row === right.startPosition.row &&
179
+ left.startPosition.column === right.startPosition.column &&
180
+ left.endPosition.row === right.endPosition.row &&
181
+ left.endPosition.column === right.endPosition.column
182
+ );
183
+ }
184
+
185
+ function shellVariable(
186
+ node: SyntaxNodeLike,
187
+ kind: "constant" | "variable",
188
+ source: string,
189
+ ): OutlineItem | null {
190
+ const name = node.childForFieldName("name");
191
+ return name?.type === "variable_name"
192
+ ? { name: name.text, kind, range: nodeToRange(node, source) }
193
+ : null;
194
+ }
195
+
196
+ function shellDeclarations(node: SyntaxNodeLike, source: string): OutlineItem[] {
197
+ if (node.children.some((child) => child.type === "word" && /^-[a-z]*[pf]/i.test(child.text))) {
198
+ return [];
199
+ }
200
+ const constant =
201
+ node.children.some((child) => child.type === "readonly") ||
202
+ node.children.some((child) => child.type === "word" && /^-[a-z]*r/i.test(child.text));
203
+ const range = nodeToRange(node, source);
204
+ return node.children.flatMap((child) => {
205
+ if (child.type === "variable_assignment") {
206
+ const item = shellVariable(child, constant ? "constant" : "variable", source);
207
+ return item ? [{ ...item, range }] : [];
208
+ }
209
+ return child.type === "variable_name"
210
+ ? [{ name: child.text, kind: constant ? "constant" : "variable", range }]
211
+ : [];
212
+ });
213
+ }
214
+
215
+ function rAssignments(node: SyntaxNodeLike, source: string): OutlineItem[] | undefined {
216
+ const operator = node.children.find((child) => R_ASSIGNMENT_OPERATORS.has(child.type))?.type;
217
+ if (!operator) return undefined;
218
+
219
+ const leftward = operator === "=" || operator.endsWith("<-");
220
+ const receiver = node.childForFieldName(leftward ? "lhs" : "rhs");
221
+ const value = node.childForFieldName(leftward ? "rhs" : "lhs");
222
+ if (!receiver || !value) return [];
223
+
224
+ const item =
225
+ receiver.type === "identifier"
226
+ ? [
227
+ {
228
+ name: receiver.text,
229
+ kind: rAssignedValue(value)?.type === "function_definition" ? "function" : "variable",
230
+ range: nodeToRange(node, source),
231
+ },
232
+ ]
233
+ : [];
234
+ const nestedAssignment = unwrapParentheses(value);
235
+ const chained =
236
+ nestedAssignment?.type === "binary_operator"
237
+ ? (rAssignments(nestedAssignment, source) ?? [])
238
+ : [];
239
+ return [...item, ...chained];
240
+ }
241
+
242
+ function unwrapParentheses(node: SyntaxNodeLike | null): SyntaxNodeLike | null {
243
+ if (node?.type === "parenthesized_expression") {
244
+ return unwrapParentheses(node.childForFieldName("body"));
245
+ }
246
+ if (node?.type === "parenthesized_statements") {
247
+ return unwrapParentheses(
248
+ node.children.find(
249
+ (child) => child.type === "assignment" || child.type === "operator_assignment",
250
+ ) ?? null,
251
+ );
252
+ }
253
+ return node;
254
+ }
255
+
256
+ function rAssignedValue(node: SyntaxNodeLike): SyntaxNodeLike | null {
257
+ const assignment = unwrapParentheses(node);
258
+ if (assignment?.type !== "binary_operator") return assignment;
259
+
260
+ const operator = assignment.children.find((child) =>
261
+ R_ASSIGNMENT_OPERATORS.has(child.type),
262
+ )?.type;
263
+ if (!operator) return assignment;
264
+ const leftward = operator === "=" || operator.endsWith("<-");
265
+ const value = assignment.childForFieldName(leftward ? "rhs" : "lhs");
266
+ return value ? rAssignedValue(value) : null;
267
+ }
268
+
269
+ function one(item: OutlineItem | null): OutlineItem[] {
270
+ return item ? [item] : [];
271
+ }
272
+
273
+ function named(node: SyntaxNodeLike, kind: string, source: string): OutlineItem | null {
274
+ const name = node.childForFieldName("name");
275
+ return name ? { name: name.text, kind, range: nodeToRange(node, source) } : null;
276
+ }
277
+
278
+ const RUBY_BINDING_TYPES = new Set([
279
+ "constant",
280
+ "identifier",
281
+ "global_variable",
282
+ "class_variable",
283
+ "instance_variable",
284
+ "scope_resolution",
285
+ ]);
286
+ const RUBY_BINDING_PATTERNS = new Set([
287
+ "left_assignment_list",
288
+ "destructured_left_assignment",
289
+ "rest_assignment",
290
+ ]);
291
+ const RUBY_CONTAINER_NAME_TYPES = new Set(["constant", "scope_resolution"]);
292
+ const RUBY_ATTRIBUTE_METHODS = new Set(["attr", "attr_reader", "attr_writer", "attr_accessor"]);
293
+ const RUBY_VISIBILITY_METHODS = new Set([
294
+ "private",
295
+ "protected",
296
+ "public",
297
+ "module_function",
298
+ "private_class_method",
299
+ "public_class_method",
300
+ ]);
301
+ const SHELL_SCOPE_BOUNDARIES = new Set([
302
+ "command_substitution",
303
+ "process_substitution",
304
+ "subshell",
305
+ "pipeline",
306
+ ]);
307
+ const R_ASSIGNMENT_OPERATORS = new Set(["=", "<-", "<<-", "->", "->>"]);
@@ -3,6 +3,7 @@
3
3
  import { nodeToRange } from "../coordinates.ts";
4
4
  import type { SyntaxNodeLike } from "../syntax-node.ts";
5
5
  import type { OutlineItem } from "../types.ts";
6
+ import { extractPolyglotOutlineItems } from "./outline-polyglot.ts";
6
7
 
7
8
  /** Node types that can be extracted directly as outline items. */
8
9
  const OUTLINE_DECLARATION_NODE_TYPES = new Set([
@@ -32,12 +33,15 @@ function collectItems(node: SyntaxNodeLike, source: string): OutlineItem[] {
32
33
  const items: OutlineItem[] = [];
33
34
 
34
35
  for (const child of node.children) {
35
- const item = extractItem(child, source);
36
- if (item) {
37
- items.push(item);
38
- } else {
39
- items.push(...collectItems(child, source));
36
+ const polyglotItems = extractPolyglotOutlineItems(child, source);
37
+ if (polyglotItems) {
38
+ items.push(...polyglotItems);
39
+ continue;
40
40
  }
41
+
42
+ const item = extractItem(child, source);
43
+ if (item) items.push(item);
44
+ else items.push(...collectItems(child, source));
41
45
  }
42
46
 
43
47
  return items;
@@ -79,7 +79,10 @@ export interface TreeSitterService {
79
79
  canParse(file: string): Promise<TreeSitterResult<{ file: string; language: string }>>;
80
80
  /** Run a Tree-sitter query and return all captures. */
81
81
  query(file: string, queryString: string): Promise<TreeSitterResult<QueryCapture[]>>;
82
- /** Extract top-level declarations plus supported class/interface/enum members. */
82
+ /**
83
+ * Extract shallow declarations, including supported nested code members,
84
+ * HTML ids, and SQL schema members.
85
+ */
83
86
  outline(file: string): Promise<TreeSitterResult<OutlineItem[]>>;
84
87
  /** Extract static ES import declarations. */
85
88
  imports(file: string): Promise<TreeSitterResult<ImportRecord[]>>;
@@ -143,7 +146,6 @@ export type SupportedExtension =
143
146
  | ".pyi"
144
147
  | ".rs"
145
148
  | ".go"
146
- | ".mod"
147
149
  | ".c"
148
150
  | ".h"
149
151
  | ".cpp"
@@ -157,9 +159,11 @@ export type SupportedExtension =
157
159
  | ".kt"
158
160
  | ".kts"
159
161
  | ".rb"
162
+ | ".gemspec"
160
163
  | ".sh"
161
164
  | ".bash"
162
165
  | ".zsh"
166
+ | ".ksh"
163
167
  | ".html"
164
168
  | ".htm"
165
169
  | ".xhtml"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-intelligence",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "SuPi Code Intelligence extension — workspace orientation, target resolution and inspection, relationship analysis, code-aware search, live health, and semantic refactoring for pi",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,10 +34,10 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "yaml": "^2.9.0",
37
- "@mrclrchtr/supi-core": "4.1.0",
38
- "@mrclrchtr/supi-tree-sitter": "4.1.0",
39
- "@mrclrchtr/supi-lsp": "4.1.0",
40
- "@mrclrchtr/supi-code-runtime": "4.1.0"
37
+ "@mrclrchtr/supi-code-runtime": "4.3.0",
38
+ "@mrclrchtr/supi-core": "4.3.0",
39
+ "@mrclrchtr/supi-lsp": "4.3.0",
40
+ "@mrclrchtr/supi-tree-sitter": "4.3.0"
41
41
  },
42
42
  "bundledDependencies": [
43
43
  "@mrclrchtr/supi-code-runtime",
@@ -344,7 +344,7 @@ async function collectMatchesForFile(
344
344
  const outline = await structural.outline(relFile);
345
345
  if (!handleStructuralResult(outline, recordFailure)) return;
346
346
  for (const item of flattenOutlineItems(outline.data)) {
347
- if (kind === "type" && !TYPE_LIKE_KINDS.has(item.kind.toLowerCase())) continue;
347
+ if (kind === "type" && !TYPE_KIND.test(item.kind.toLowerCase())) continue;
348
348
  if (kind === "interface" && item.kind.toLowerCase() !== "interface") continue;
349
349
  if (kind === "class" && item.kind.toLowerCase() !== "class") continue;
350
350
  if (kind === "method" && item.kind.toLowerCase() !== "method") continue;
@@ -368,7 +368,7 @@ function handleStructuralResult<T>(
368
368
  return false;
369
369
  }
370
370
 
371
- const TYPE_LIKE_KINDS = new Set(["class", "interface", "type", "enum"]);
371
+ const TYPE_KIND = /^(?:class|interface|type|enum|struct|union|record|object|concept)$/;
372
372
 
373
373
  const AST_KIND_OPERATIONS = {
374
374
  definition: "outline",
@@ -117,7 +117,10 @@ export async function resolveSymbolTarget(
117
117
  };
118
118
  }
119
119
  if (result.data.length === 0) {
120
- return { kind: "error", message: `Symbol not found: \`${symbol}\`` };
120
+ return {
121
+ kind: "error",
122
+ message: `Symbol not found: \`${symbol}\`. Try \`code_resolve\` to search for it, or use an anchor target (file/line/character) if you know the location.`,
123
+ };
121
124
  }
122
125
 
123
126
  const scopePath = options?.path ? normalizePath(options.path, cwd) : null;
@@ -130,7 +133,7 @@ export async function resolveSymbolTarget(
130
133
  if (eligible.length === 0) {
131
134
  return {
132
135
  kind: "error",
133
- message: `Symbol not found: \`${symbol}\`${scopePath ? ` in path \`${options?.path}\`` : ""}`,
136
+ message: `Symbol not found: \`${symbol}\`${scopePath ? ` in path \`${options?.path}\`` : ""}. Try \`code_resolve\` to search for it, or use an anchor target (file/line/character) if you know the location.`,
134
137
  };
135
138
  }
136
139
 
package/src/extension.ts CHANGED
@@ -43,7 +43,11 @@ export default function codeIntelligenceExtension(
43
43
  );
44
44
 
45
45
  registerCiStatusCommand(pi);
46
- registerLspFooterContribution(lspState);
46
+ const lspFooter = registerLspFooterContribution(pi, lspState);
47
+
48
+ pi.on("session_shutdown", () => {
49
+ lspFooter.dispose();
50
+ });
47
51
 
48
52
  pi.on("before_agent_start", (event, ctx) => {
49
53
  const session = app.getSession(ctx.cwd) ?? app.createSession(ctx.cwd);