@mrclrchtr/supi-code-intelligence 4.0.0 → 4.2.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.
- package/README.md +8 -3
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -5
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +5 -5
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -5
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +2 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +19 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-c-family.ts +230 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +186 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-jvm.ts +311 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-polyglot.ts +218 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-scripting.ts +307 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +9 -5
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +6 -2
- package/package.json +6 -5
- package/src/analysis/search/pattern.ts +2 -2
- package/src/analysis/target/symbol.ts +5 -2
- package/src/app/app.ts +4 -1
- package/src/extension.ts +6 -84
- package/src/headless.ts +41 -0
- package/src/session/orientation/collect.ts +134 -180
- package/src/session/orientation/context-sections.ts +14 -48
- package/src/session/orientation-types.ts +14 -4
- package/src/session/orientation-workflow.ts +20 -48
- package/src/substrate/lsp/lifecycle.ts +25 -39
- package/src/substrate/lsp/state.ts +3 -0
- package/src/substrate/workspace-provider-host.ts +129 -0
- package/src/tool/graph/execute.ts +1 -1
- package/src/tool/graph/markdown.ts +222 -0
- package/src/tool/orientation/markdown.ts +66 -11
- package/src/tool/register.ts +10 -0
- package/src/ui/markdown/overview-data.ts +3 -15
- package/src/ui/markdown/overview.ts +4 -10
- package/src/ui/markdown/types.ts +0 -1
- package/src/substrate/tree-sitter/lifecycle.ts +0 -52
- package/src/tool/graph/calls-md.ts +0 -41
- package/src/tool/graph/implementations-md.ts +0 -57
- package/src/tool/graph/markdown-base.ts +0 -88
- package/src/tool/graph/provider-location-md.ts +0 -9
- package/src/tool/graph/references-md.ts +0 -41
|
@@ -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
|
|
36
|
-
if (
|
|
37
|
-
items.push(
|
|
38
|
-
|
|
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
|
-
/**
|
|
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.
|
|
3
|
+
"version": "4.2.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-code-runtime": "4.
|
|
38
|
-
"@mrclrchtr/supi-lsp": "4.
|
|
39
|
-
"@mrclrchtr/supi-
|
|
40
|
-
"@mrclrchtr/supi-
|
|
37
|
+
"@mrclrchtr/supi-code-runtime": "4.2.0",
|
|
38
|
+
"@mrclrchtr/supi-lsp": "4.2.0",
|
|
39
|
+
"@mrclrchtr/supi-tree-sitter": "4.2.0",
|
|
40
|
+
"@mrclrchtr/supi-core": "4.2.0"
|
|
41
41
|
},
|
|
42
42
|
"bundledDependencies": [
|
|
43
43
|
"@mrclrchtr/supi-code-runtime",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
".": "./src/index.ts",
|
|
78
78
|
"./api": "./src/api.ts",
|
|
79
79
|
"./extension": "./src/extension.ts",
|
|
80
|
+
"./headless": "./src/headless.ts",
|
|
80
81
|
"./package.json": "./package.json"
|
|
81
82
|
}
|
|
82
83
|
}
|
|
@@ -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" && !
|
|
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
|
|
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 {
|
|
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/app/app.ts
CHANGED
|
@@ -61,7 +61,10 @@ export function createSessionCache(): {
|
|
|
61
61
|
const sessions = new Map<string, WorkspaceCodeIntelligenceSession>();
|
|
62
62
|
return {
|
|
63
63
|
getOrCreate: (cwd) => getOrCreateSession(sessions, cwd),
|
|
64
|
-
clear: () =>
|
|
64
|
+
clear: () => {
|
|
65
|
+
for (const session of sessions.values()) session.clearStores();
|
|
66
|
+
sessions.clear();
|
|
67
|
+
},
|
|
65
68
|
};
|
|
66
69
|
}
|
|
67
70
|
|
package/src/extension.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Code Intelligence extension entry point — registers the focused code-intelligence tools,
|
|
2
|
-
//
|
|
2
|
+
// shared provider lifecycle, settings, UI, and the unified /supi-ci-status command.
|
|
3
3
|
|
|
4
4
|
import type { BeforeAgentStartEventResult, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { buildArchitectureModel } from "./analysis/architecture/discovery.ts";
|
|
@@ -10,10 +10,6 @@ import { registerLspSessionLifecycle } from "./substrate/lsp/lifecycle.ts";
|
|
|
10
10
|
import { registerWorkspaceRecoveryHandler } from "./substrate/lsp/recovery.ts";
|
|
11
11
|
import { registerLspSettings } from "./substrate/lsp/settings.ts";
|
|
12
12
|
import { createLspAdapterState } from "./substrate/lsp/state.ts";
|
|
13
|
-
import {
|
|
14
|
-
createTsAdapterState,
|
|
15
|
-
registerTsSessionLifecycle,
|
|
16
|
-
} from "./substrate/tree-sitter/lifecycle.ts";
|
|
17
13
|
import { registerCodeIntelligenceTools } from "./tool/register.ts";
|
|
18
14
|
import { registerLspFooterContribution } from "./ui/footer.ts";
|
|
19
15
|
import { estimateTokens, OVERVIEW_TOKEN_BUDGET, renderOverview } from "./ui/markdown/overview.ts";
|
|
@@ -21,102 +17,44 @@ import { buildOverviewData } from "./ui/markdown/overview-data.ts";
|
|
|
21
17
|
import { registerCiStatusCommand } from "./ui/status-command.ts";
|
|
22
18
|
|
|
23
19
|
const OVERVIEW_CUSTOM_TYPE = "code-intelligence-overview";
|
|
24
|
-
const PROCESS_EXIT_SAFETY_NET = Symbol.for("supi-code-intelligence/process-exit-safety-net");
|
|
25
|
-
|
|
26
|
-
interface ProcessExitSafetyNet {
|
|
27
|
-
cleanup: (() => void) | null;
|
|
28
|
-
handlerRegistered: boolean;
|
|
29
|
-
handler: () => void;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Keep one process-exit listener across `/reload` extension instances.
|
|
34
|
-
* Pi hosts one active extension API per process; replacing its cleanup callback
|
|
35
|
-
* prevents old adapter state from being retained after reload.
|
|
36
|
-
*/
|
|
37
|
-
function registerProcessExitSafetyNet(cleanup: () => void): () => void {
|
|
38
|
-
const host = globalThis as Record<symbol, ProcessExitSafetyNet | undefined>;
|
|
39
|
-
const safetyNet = host[PROCESS_EXIT_SAFETY_NET] ?? createProcessExitSafetyNet();
|
|
40
|
-
host[PROCESS_EXIT_SAFETY_NET] = safetyNet;
|
|
41
|
-
|
|
42
|
-
safetyNet.cleanup = cleanup;
|
|
43
|
-
if (!safetyNet.handlerRegistered) {
|
|
44
|
-
process.once("exit", safetyNet.handler);
|
|
45
|
-
safetyNet.handlerRegistered = true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return () => {
|
|
49
|
-
if (safetyNet.cleanup !== cleanup) return;
|
|
50
|
-
safetyNet.cleanup = null;
|
|
51
|
-
if (!safetyNet.handlerRegistered) return;
|
|
52
|
-
process.off("exit", safetyNet.handler);
|
|
53
|
-
safetyNet.handlerRegistered = false;
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function createProcessExitSafetyNet(): ProcessExitSafetyNet {
|
|
58
|
-
const safetyNet: ProcessExitSafetyNet = {
|
|
59
|
-
cleanup: null,
|
|
60
|
-
handlerRegistered: false,
|
|
61
|
-
handler: () => {},
|
|
62
|
-
};
|
|
63
|
-
safetyNet.handler = () => {
|
|
64
|
-
safetyNet.handlerRegistered = false;
|
|
65
|
-
const activeCleanup = safetyNet.cleanup;
|
|
66
|
-
safetyNet.cleanup = null;
|
|
67
|
-
activeCleanup?.();
|
|
68
|
-
};
|
|
69
|
-
return safetyNet;
|
|
70
|
-
}
|
|
71
20
|
|
|
21
|
+
/** Register the full interactive Code Intelligence profile. */
|
|
72
22
|
export default function codeIntelligenceExtension(
|
|
73
23
|
pi: ExtensionAPI,
|
|
74
24
|
getOrCreateSession?: (cwd: string) => WorkspaceCodeIntelligenceSession,
|
|
75
25
|
) {
|
|
76
26
|
const app = createCodeIntelligenceApp(pi);
|
|
77
|
-
|
|
78
27
|
const lspState = createLspAdapterState();
|
|
79
|
-
const tsState = createTsAdapterState();
|
|
80
28
|
|
|
81
|
-
// ── Substrate wiring ──────────────────────────────────────────────
|
|
82
29
|
registerCodeIntelligenceSettings(pi);
|
|
83
30
|
registerLspSettings(pi);
|
|
84
|
-
registerLspSessionLifecycle(pi, lspState)
|
|
85
|
-
registerWorkspaceRecoveryHandler(pi, lspState);
|
|
86
|
-
registerTsSessionLifecycle(pi, tsState);
|
|
87
|
-
|
|
88
|
-
// ── Attach controller refs to sessions on startup (ADR 0008) ──────
|
|
89
|
-
pi.on("session_start", (_event, ctx) => {
|
|
31
|
+
registerLspSessionLifecycle(pi, lspState, (ctx) => {
|
|
90
32
|
const session = app.getSession(ctx.cwd);
|
|
91
33
|
if (!session) return;
|
|
92
34
|
session.attachLspController(lspState.controller);
|
|
93
35
|
session.seedSentinelSnapshot(lspState.sentinelSnapshot);
|
|
94
36
|
session.setProjectTrusted(ctx.isProjectTrusted());
|
|
95
37
|
});
|
|
38
|
+
registerWorkspaceRecoveryHandler(pi, lspState);
|
|
96
39
|
|
|
97
|
-
// ── Tool registration ─────────────────────────────────────────────
|
|
98
40
|
registerCodeIntelligenceTools(
|
|
99
41
|
pi,
|
|
100
42
|
getOrCreateSession ?? ((cwd) => app.getSession(cwd) ?? app.createSession(cwd)),
|
|
101
43
|
);
|
|
102
44
|
|
|
103
|
-
// ── UI registration ───────────────────────────────────────────────
|
|
104
45
|
registerCiStatusCommand(pi);
|
|
105
46
|
registerLspFooterContribution(lspState);
|
|
106
47
|
|
|
107
|
-
// ── Native context path capture for instruction-file dedup ─────────
|
|
108
48
|
pi.on("before_agent_start", (event, ctx) => {
|
|
109
49
|
const session = app.getSession(ctx.cwd) ?? app.createSession(ctx.cwd);
|
|
110
50
|
session.captureNativeInstructionPaths(event.systemPromptOptions.contextFiles ?? []);
|
|
111
51
|
});
|
|
112
52
|
|
|
113
|
-
// ── Overview injection — uses the app-managed session state ────────
|
|
114
53
|
pi.on(
|
|
115
54
|
"before_agent_start",
|
|
116
55
|
async (_event, ctx): Promise<BeforeAgentStartEventResult | undefined> => {
|
|
117
56
|
const session = app.getSession(ctx.cwd);
|
|
118
|
-
if (!session) return;
|
|
119
|
-
if (!session.claimOverviewInjection()) return;
|
|
57
|
+
if (!session?.claimOverviewInjection()) return;
|
|
120
58
|
|
|
121
59
|
const model = await buildArchitectureModel(ctx.cwd);
|
|
122
60
|
if (model.modules.length === 0) return;
|
|
@@ -138,24 +76,8 @@ export default function codeIntelligenceExtension(
|
|
|
138
76
|
}
|
|
139
77
|
|
|
140
78
|
return {
|
|
141
|
-
message: {
|
|
142
|
-
customType: OVERVIEW_CUSTOM_TYPE,
|
|
143
|
-
display: false,
|
|
144
|
-
content: overview,
|
|
145
|
-
},
|
|
79
|
+
message: { customType: OVERVIEW_CUSTOM_TYPE, display: false, content: overview },
|
|
146
80
|
};
|
|
147
81
|
},
|
|
148
82
|
);
|
|
149
|
-
|
|
150
|
-
// ── Process-exit safety net ─────────────────────────────────────────
|
|
151
|
-
let cleaningUp = false;
|
|
152
|
-
const unregisterExitSafetyNet = registerProcessExitSafetyNet(() => {
|
|
153
|
-
if (cleaningUp) return;
|
|
154
|
-
cleaningUp = true;
|
|
155
|
-
// Exit handlers cannot await. Controller shutdown begins synchronous
|
|
156
|
-
// process cleanup before its first await.
|
|
157
|
-
void lspState.controller?.shutdown();
|
|
158
|
-
void tsState.controller?.shutdown();
|
|
159
|
-
});
|
|
160
|
-
pi.on("session_shutdown", unregisterExitSafetyNet);
|
|
161
83
|
}
|
package/src/headless.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { createSessionCache } from "./app/app.ts";
|
|
3
|
+
import { registerLspSessionLifecycle } from "./substrate/lsp/lifecycle.ts";
|
|
4
|
+
import { createLspAdapterState } from "./substrate/lsp/state.ts";
|
|
5
|
+
import { registerCodeIntelligenceTools } from "./tool/register.ts";
|
|
6
|
+
import { CODE_INTELLIGENCE_TOOL_SPECS } from "./tool/specs.ts";
|
|
7
|
+
|
|
8
|
+
/** Exact Code Intelligence tool surface available to managed Reviewer Sessions. */
|
|
9
|
+
export const HEADLESS_INSPECTION_TOOL_NAMES = [
|
|
10
|
+
"code_resolve",
|
|
11
|
+
"code_inspect",
|
|
12
|
+
"code_orientation",
|
|
13
|
+
"code_graph",
|
|
14
|
+
"code_find",
|
|
15
|
+
"code_health",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
const inspectionToolNames = new Set<string>(HEADLESS_INSPECTION_TOOL_NAMES);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register the managed-child profile: six inspection tools backed by the shared
|
|
22
|
+
* Workspace provider host, without refactors, settings, UI, or overview state.
|
|
23
|
+
*/
|
|
24
|
+
export default function headlessInspectionProfile(pi: ExtensionAPI): void {
|
|
25
|
+
const sessions = createSessionCache();
|
|
26
|
+
const lspState = createLspAdapterState();
|
|
27
|
+
registerLspSessionLifecycle(pi, lspState, (ctx) => {
|
|
28
|
+
const session = sessions.getOrCreate(ctx.cwd);
|
|
29
|
+
session.attachLspController(lspState.controller);
|
|
30
|
+
session.seedSentinelSnapshot(lspState.sentinelSnapshot);
|
|
31
|
+
session.setProjectTrusted(ctx.isProjectTrusted());
|
|
32
|
+
});
|
|
33
|
+
pi.on("session_shutdown", () => sessions.clear());
|
|
34
|
+
|
|
35
|
+
registerCodeIntelligenceTools(
|
|
36
|
+
pi,
|
|
37
|
+
sessions.getOrCreate,
|
|
38
|
+
undefined,
|
|
39
|
+
CODE_INTELLIGENCE_TOOL_SPECS.filter((spec) => inspectionToolNames.has(spec.name)),
|
|
40
|
+
);
|
|
41
|
+
}
|