@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.
Files changed (68) hide show
  1. package/README.md +8 -3
  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/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  6. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  7. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  8. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  10. package/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -5
  12. 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
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  20. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  21. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +2 -2
  22. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +5 -5
  23. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -5
  24. 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
  25. 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
  26. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  27. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  28. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  29. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +1 -0
  30. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +9 -0
  31. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings.ts +1 -0
  32. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  33. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +2 -1
  34. package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +19 -0
  35. package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +1 -0
  36. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-c-family.ts +230 -0
  37. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +186 -0
  38. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-jvm.ts +311 -0
  39. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-polyglot.ts +218 -0
  40. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-scripting.ts +307 -0
  41. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +9 -5
  42. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +6 -2
  43. package/package.json +6 -5
  44. package/src/analysis/search/pattern.ts +2 -2
  45. package/src/analysis/target/symbol.ts +5 -2
  46. package/src/app/app.ts +4 -1
  47. package/src/extension.ts +6 -84
  48. package/src/headless.ts +41 -0
  49. package/src/session/orientation/collect.ts +134 -180
  50. package/src/session/orientation/context-sections.ts +14 -48
  51. package/src/session/orientation-types.ts +14 -4
  52. package/src/session/orientation-workflow.ts +20 -48
  53. package/src/substrate/lsp/lifecycle.ts +25 -39
  54. package/src/substrate/lsp/state.ts +3 -0
  55. package/src/substrate/workspace-provider-host.ts +129 -0
  56. package/src/tool/graph/execute.ts +1 -1
  57. package/src/tool/graph/markdown.ts +222 -0
  58. package/src/tool/orientation/markdown.ts +66 -11
  59. package/src/tool/register.ts +10 -0
  60. package/src/ui/markdown/overview-data.ts +3 -15
  61. package/src/ui/markdown/overview.ts +4 -10
  62. package/src/ui/markdown/types.ts +0 -1
  63. package/src/substrate/tree-sitter/lifecycle.ts +0 -52
  64. package/src/tool/graph/calls-md.ts +0 -41
  65. package/src/tool/graph/implementations-md.ts +0 -57
  66. package/src/tool/graph/markdown-base.ts +0 -88
  67. package/src/tool/graph/provider-location-md.ts +0 -9
  68. 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 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.0.0",
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.0.0",
38
- "@mrclrchtr/supi-lsp": "4.0.0",
39
- "@mrclrchtr/supi-core": "4.0.0",
40
- "@mrclrchtr/supi-tree-sitter": "4.0.0"
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" && !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/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: () => sessions.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
- // LSP diagnostics and settings, and the unified /supi-ci-status command.
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
  }
@@ -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
+ }