@mrclrchtr/supi-code-intelligence 6.3.0 → 7.0.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 (136) hide show
  1. package/README.md +164 -82
  2. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  4. package/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  5. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  6. package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  7. package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  9. package/node_modules/@mrclrchtr/supi-lsp/README.md +35 -13
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +3 -1
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +3 -1
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +1 -1
  20. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  21. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +43 -35
  23. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +83 -17
  24. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +108 -92
  25. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +44 -82
  26. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +3 -4
  27. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +9 -68
  28. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +45 -58
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +230 -331
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +201 -1
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +129 -70
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-typescript.ts +395 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +8 -12
  34. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +685 -183
  35. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +2 -2
  36. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +60 -60
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-request-enrollment.ts +75 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-barrier.ts +443 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-errors.ts +30 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-semantic-input-file.ts +15 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +290 -37
  42. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +72 -16
  43. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +1 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +30 -4
  45. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +1 -1
  46. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +4 -32
  47. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sources.ts +108 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +10 -4
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +50 -1
  50. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +11 -7
  51. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +770 -56
  52. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +3 -1
  53. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +37 -4
  54. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +48 -12
  55. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +50 -4
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +8 -1
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +8 -0
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +10 -3
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/llm.ts +141 -16
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +2 -0
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callee-display-name.ts +165 -0
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +67 -9
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +2 -0
  68. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  69. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  70. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  71. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  72. package/node_modules/yaml/package.json +1 -1
  73. package/package.json +5 -5
  74. package/src/analysis/health/diagnostics.ts +1 -2
  75. package/src/analysis/health/file-scope.ts +4 -1
  76. package/src/analysis/health/server-status.ts +1 -1
  77. package/src/analysis/relations/callees.ts +17 -8
  78. package/src/analysis/relations/implementations.ts +1 -2
  79. package/src/analysis/relations/references.ts +8 -4
  80. package/src/analysis/relations/types.ts +6 -30
  81. package/src/analysis/target/anchored.ts +9 -1
  82. package/src/analysis/target/symbol.ts +29 -11
  83. package/src/analysis/target/types.ts +14 -6
  84. package/src/api.ts +2 -0
  85. package/src/session/graph/collect.ts +18 -3
  86. package/src/session/health-refresh.ts +27 -11
  87. package/src/session/health-types.ts +21 -4
  88. package/src/session/health-workflow.ts +28 -22
  89. package/src/session/orientation-types.ts +5 -6
  90. package/src/session/orientation-workflow.ts +10 -2
  91. package/src/session/session.ts +12 -5
  92. package/src/session/target-workflow.ts +9 -6
  93. package/src/substrate/lsp/maintenance.ts +53 -47
  94. package/src/substrate/lsp/source-tracking.ts +252 -0
  95. package/src/tool/code_find/guidance.ts +3 -3
  96. package/src/tool/code_find/spec.ts +5 -4
  97. package/src/tool/code_find/tui.ts +7 -1
  98. package/src/tool/code_graph/details.ts +32 -0
  99. package/src/tool/code_graph/display.ts +54 -0
  100. package/src/tool/code_graph/format.ts +60 -0
  101. package/src/tool/code_graph/guidance.ts +2 -2
  102. package/src/tool/code_graph/markdown.ts +76 -198
  103. package/src/tool/code_graph/read-next.ts +65 -0
  104. package/src/tool/code_graph/result.ts +118 -83
  105. package/src/tool/code_graph/tui.ts +216 -54
  106. package/src/tool/code_health/guidance.ts +3 -5
  107. package/src/tool/code_health/markdown.ts +90 -57
  108. package/src/tool/code_health/refresh-outcome.ts +51 -0
  109. package/src/tool/code_health/refresh-status.ts +130 -43
  110. package/src/tool/code_health/scope.ts +78 -0
  111. package/src/tool/code_health/spec.ts +1 -1
  112. package/src/tool/code_health/tui.ts +29 -38
  113. package/src/tool/code_inspect/guidance.ts +5 -3
  114. package/src/tool/code_inspect/tui.ts +7 -1
  115. package/src/tool/code_orientation/guidance.ts +3 -4
  116. package/src/tool/code_orientation/result.ts +31 -8
  117. package/src/tool/code_orientation/tui.ts +26 -5
  118. package/src/tool/code_refactor_apply/guidance.ts +1 -1
  119. package/src/tool/code_refactor_apply/tui.ts +7 -3
  120. package/src/tool/code_refactor_plan/guidance.ts +3 -5
  121. package/src/tool/code_refactor_plan/tui.ts +7 -1
  122. package/src/tool/code_resolve/guidance.ts +3 -5
  123. package/src/tool/code_resolve/result.ts +13 -10
  124. package/src/tool/code_resolve/tui.ts +18 -1
  125. package/src/tool/guidance.ts +11 -9
  126. package/src/tool/infra/truncate.ts +39 -13
  127. package/src/tool/result/candidate-row.ts +27 -0
  128. package/src/tool/result/errors.ts +21 -6
  129. package/src/tool/result/types.ts +7 -3
  130. package/src/tool/schemas.ts +5 -0
  131. package/src/types/execution.ts +2 -0
  132. package/src/types/index.ts +2 -0
  133. package/src/ui/tui/candidate-legacy-body.ts +69 -0
  134. package/src/ui/tui/candidate-selection.ts +366 -0
  135. package/src/ui/tui/common.ts +26 -31
  136. package/src/ui/tui/execution-error.ts +92 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "6.3.0",
3
+ "version": "7.0.0",
4
4
  "description": "Structural AST analysis for SuPi code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,8 +36,8 @@
36
36
  "dependencies": {
37
37
  "jiti": "^2.7.0",
38
38
  "web-tree-sitter": "^0.27.0",
39
- "@mrclrchtr/supi-code-runtime": "6.3.0",
40
- "@mrclrchtr/supi-core": "6.3.0"
39
+ "@mrclrchtr/supi-core": "7.0.0",
40
+ "@mrclrchtr/supi-code-runtime": "7.0.0"
41
41
  },
42
42
  "bundledDependencies": [
43
43
  "@mrclrchtr/supi-code-runtime",
@@ -145,7 +145,9 @@ function mapCalleesAtResult(result: CalleesAtResult): CalleesData {
145
145
  },
146
146
  callees: result.callees.map((c) => ({
147
147
  name: c.name,
148
+ ...(c.displayName === undefined ? {} : { displayName: c.displayName }),
148
149
  startLine: c.range.startLine,
150
+ startCharacter: c.range.startCharacter,
149
151
  })),
150
152
  depth: result.depth,
151
153
  };
@@ -0,0 +1,165 @@
1
+ import type { SyntaxNodeLike } from "../syntax-node.ts";
2
+ import type { GrammarId } from "../types.ts";
3
+ import { normalizeCallName } from "./call-name.ts";
4
+
5
+ type CalleeSyntaxChild = SyntaxNodeLike & {
6
+ readonly isExtra: boolean;
7
+ };
8
+
9
+ type CalleeSyntaxNode = SyntaxNodeLike & {
10
+ readonly namedChildren: CalleeSyntaxChild[];
11
+ };
12
+
13
+ type SourcePoint = { row: number; column: number };
14
+
15
+ type TextReplacement = { start: number; end: number };
16
+
17
+ type CalleeDisplayNameOptions = {
18
+ node: SyntaxNodeLike;
19
+ grammarId: GrammarId;
20
+ nodeType: string;
21
+ source: string;
22
+ sourceLineStarts: readonly number[];
23
+ };
24
+
25
+ const CALL_NODE_TYPES = new Set([
26
+ "call_expression",
27
+ "call",
28
+ "method_invocation",
29
+ "new_expression",
30
+ "object_creation_expression",
31
+ ]);
32
+
33
+ const ARGUMENT_LIST_TYPES = new Set(["arguments", "argument_list", "value_arguments"]);
34
+
35
+ const RECEIVER_FIELDS = [
36
+ "function",
37
+ "constructor",
38
+ "object",
39
+ "receiver",
40
+ "operand",
41
+ "value",
42
+ "lhs",
43
+ "argument",
44
+ ] as const;
45
+
46
+ const RECEIVER_WRAPPER_TYPES = new Set(["navigation_expression", "parenthesized_expression"]);
47
+
48
+ /**
49
+ * Create a syntax-shortened label for one structural callee capture.
50
+ *
51
+ * The full normalized name remains the identity-bearing value. This helper
52
+ * only replaces argument-list nodes on the receiver chain with `(…)`; it does
53
+ * not inspect arbitrary expression text to find parentheses.
54
+ */
55
+ export function createCalleeDisplayName({
56
+ node,
57
+ grammarId,
58
+ nodeType,
59
+ source,
60
+ sourceLineStarts,
61
+ }: CalleeDisplayNameOptions): string | undefined {
62
+ const replacements: TextReplacement[] = [];
63
+ collectReceiverArgumentLists(node, replacements, sourceLineStarts);
64
+ const selected = selectNonOverlappingReplacements(replacements);
65
+ const start = sourceOffset(node.startPosition, sourceLineStarts);
66
+ const end = sourceOffset(node.endPosition, sourceLineStarts);
67
+ if (start < 0 || end < start || end > source.length) return undefined;
68
+
69
+ let rendered = source.slice(start, end);
70
+ for (let index = selected.length - 1; index >= 0; index -= 1) {
71
+ const replacement = selected[index];
72
+ if (!replacement || replacement.start < start || replacement.end > end) continue;
73
+ const relativeStart = replacement.start - start;
74
+ const relativeEnd = replacement.end - start;
75
+ rendered = `${rendered.slice(0, relativeStart)}(…)${rendered.slice(relativeEnd)}`;
76
+ }
77
+
78
+ const displayName = normalizeCallName(rendered, grammarId, nodeType);
79
+ return displayName.length > 0 ? displayName : undefined;
80
+ }
81
+
82
+ function collectReceiverArgumentLists(
83
+ node: SyntaxNodeLike,
84
+ replacements: TextReplacement[],
85
+ sourceLineStarts: readonly number[],
86
+ ): void {
87
+ const argumentList = findArgumentList(node);
88
+ if (argumentList && hasNamedChildren(argumentList)) {
89
+ const start = sourceOffset(argumentList.startPosition, sourceLineStarts);
90
+ const end = sourceOffset(argumentList.endPosition, sourceLineStarts);
91
+ if (start >= 0 && end >= start) replacements.push({ start, end });
92
+ }
93
+
94
+ for (const receiver of receiverNodes(node)) {
95
+ collectReceiverArgumentLists(receiver, replacements, sourceLineStarts);
96
+ }
97
+ }
98
+
99
+ function findArgumentList(node: SyntaxNodeLike): SyntaxNodeLike | null {
100
+ if (!CALL_NODE_TYPES.has(node.type)) return null;
101
+
102
+ const field = node.childForFieldName("arguments");
103
+ if (field) return field;
104
+
105
+ const directArgumentList = node.children.find((child) => ARGUMENT_LIST_TYPES.has(child.type));
106
+ if (directArgumentList) return directArgumentList;
107
+
108
+ if (node.type !== "call_expression") return null;
109
+ const callSuffix = node.children.find((child) => child.type === "call_suffix");
110
+ return callSuffix?.children.find((child) => child.type === "value_arguments") ?? null;
111
+ }
112
+
113
+ function receiverNodes(node: SyntaxNodeLike): SyntaxNodeLike[] {
114
+ for (const fieldName of RECEIVER_FIELDS) {
115
+ const field = node.childForFieldName(fieldName);
116
+ if (field) return [field];
117
+ }
118
+
119
+ if (node.type === "call_expression") {
120
+ return namedChildren(node).filter(
121
+ (child) => child.type !== "call_suffix" && !ARGUMENT_LIST_TYPES.has(child.type),
122
+ );
123
+ }
124
+
125
+ if (!RECEIVER_WRAPPER_TYPES.has(node.type)) return [];
126
+ return namedChildren(node).filter((child) => !ARGUMENT_LIST_TYPES.has(child.type));
127
+ }
128
+
129
+ function namedChildren(node: SyntaxNodeLike): CalleeSyntaxChild[] {
130
+ return (node as CalleeSyntaxNode).namedChildren ?? [];
131
+ }
132
+
133
+ function hasNamedChildren(node: SyntaxNodeLike): boolean {
134
+ return namedChildren(node).some((child) => !child.isExtra);
135
+ }
136
+
137
+ function selectNonOverlappingReplacements(
138
+ replacements: readonly TextReplacement[],
139
+ ): TextReplacement[] {
140
+ const selected: TextReplacement[] = [];
141
+ const ordered = [...replacements].sort(
142
+ (left, right) => left.start - right.start || right.end - left.end,
143
+ );
144
+
145
+ for (const replacement of ordered) {
146
+ const previous = selected[selected.length - 1];
147
+ if (previous && replacement.start < previous.end) continue;
148
+ selected.push(replacement);
149
+ }
150
+ return selected;
151
+ }
152
+
153
+ /** Build source offsets for Tree-sitter rows without decoding expression text. */
154
+ export function createSourceLineStarts(source: string): number[] {
155
+ const lineStarts = [0];
156
+ for (let index = 0; index < source.length; index += 1) {
157
+ if (source[index] === "\n") lineStarts.push(index + 1);
158
+ }
159
+ return lineStarts;
160
+ }
161
+
162
+ function sourceOffset(point: SourcePoint, lineStarts: readonly number[]): number {
163
+ const lineStart = lineStarts[point.row];
164
+ return lineStart === undefined ? -1 : lineStart + point.column;
165
+ }
@@ -1,11 +1,14 @@
1
+ // biome-ignore-all lint/style/noExcessiveLinesPerFile: callee scope filtering and syntax display mapping stay together.
1
2
  // Structural callee extraction — enclosing-scope lookup with per-language queries.
2
3
 
3
4
  import type { CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
4
5
  import { validatePublicPositionBounds } from "../coordinates.ts";
5
6
  import { detectGrammar } from "../language.ts";
7
+ import type { SyntaxNodeLike } from "../syntax-node.ts";
6
8
  import type { GrammarId, SourceRange, TreeSitterResult } from "../types.ts";
7
9
  import { queryParsedFile, type TreeSitterRuntime } from "../worker/runtime.ts";
8
10
  import { normalizeCallName } from "./call-name.ts";
11
+ import { createCalleeDisplayName, createSourceLineStarts } from "./callee-display-name.ts";
9
12
  import { extractScopeName } from "./scope.ts";
10
13
 
11
14
  /** Result shape returned by lookupCalleesAt. */
@@ -16,6 +19,7 @@ export interface CalleesAtResult {
16
19
  };
17
20
  callees: Array<{
18
21
  name: string;
22
+ displayName?: string;
19
23
  range: SourceRange;
20
24
  }>;
21
25
  depth: "direct" | "deep";
@@ -138,7 +142,7 @@ function findEnclosingScope(
138
142
  * 3. Runs a grammar-specific callee query.
139
143
  * 4. Filters to captures within that enclosing scope.
140
144
  * 5. Excludes nested function/method/callback scopes that do not contain the anchor.
141
- * 6. Deduplicates by name.
145
+ * 6. Deduplicates identical capture ranges and returns captures in source order.
142
146
  */
143
147
  // biome-ignore lint/complexity/useMaxParams: provider function needs runtime + coordinates + depth
144
148
  export async function lookupCalleesAt(
@@ -212,6 +216,7 @@ export async function lookupCalleesAt(
212
216
  scopes,
213
217
  tsPoint,
214
218
  depth,
219
+ source,
215
220
  );
216
221
 
217
222
  const enclosingRange = nodeToSourceRange(enclosingNode);
@@ -318,7 +323,8 @@ function filterCalleeCaptures(
318
323
  scopeTypes: ReadonlySet<string>,
319
324
  anchor: { row: number; column: number },
320
325
  depth: "direct" | "deep" = "direct",
321
- ): Array<{ name: string; range: SourceRange }> {
326
+ source: string,
327
+ ): Array<{ name: string; displayName?: string; range: SourceRange }> {
322
328
  const excludeRanges: Array<{
323
329
  startRow: number;
324
330
  startColumn: number;
@@ -329,8 +335,11 @@ function filterCalleeCaptures(
329
335
  collectInnerScopes(enclosingNode, scopeTypes, anchor, excludeRanges);
330
336
  }
331
337
 
332
- const seen = new Set<string>();
333
- const callees: Array<{ name: string; range: SourceRange }> = [];
338
+ const seenRanges = new Set<string>();
339
+ const callees: Array<{ name: string; displayName?: string; range: SourceRange }> = [];
340
+ const sourceLineStarts = createSourceLineStarts(source);
341
+ const syntaxNodes = new Map<string, SyntaxNodeLike>();
342
+ indexSyntaxNodes(enclosingNode as NamedSyntaxNode, syntaxNodes);
334
343
 
335
344
  const enclosingStartRow = enclosingNode.startPosition.row;
336
345
  const enclosingEndRow = enclosingNode.endPosition.row;
@@ -366,17 +375,66 @@ function filterCalleeCaptures(
366
375
  }
367
376
 
368
377
  const name = normalizeCallName(capture.text, grammarId, capture.nodeType);
369
- if (name.length === 0 || seen.has(name)) continue;
370
- seen.add(name);
371
-
372
- callees.push({ name, range: capture.range });
378
+ const rangeKey = sourceRangeKey(capture.range);
379
+ if (name.length === 0 || seenRanges.has(rangeKey)) continue;
380
+ seenRanges.add(rangeKey);
381
+
382
+ const syntaxNode = syntaxNodes.get(captureNodeKey(capture));
383
+ const displayName = syntaxNode
384
+ ? createCalleeDisplayName({
385
+ node: syntaxNode,
386
+ grammarId,
387
+ nodeType: capture.nodeType,
388
+ source,
389
+ sourceLineStarts,
390
+ })
391
+ : undefined;
392
+ callees.push({
393
+ name,
394
+ ...(displayName === undefined ? {} : { displayName }),
395
+ range: capture.range,
396
+ });
373
397
  }
374
398
 
375
- return callees;
399
+ return callees.sort((left, right) => compareSourceRanges(left.range, right.range));
376
400
  }
377
401
 
378
402
  // ── Internal helpers ──────────────────────────────────────────────────
379
403
 
404
+ function sourceRangeKey(range: SourceRange): string {
405
+ return `${range.startLine}:${range.startCharacter}:${range.endLine}:${range.endCharacter}`;
406
+ }
407
+
408
+ type NamedSyntaxNode = SyntaxNodeLike & { namedChildren: NamedSyntaxNode[] };
409
+
410
+ /** Query captures use named nodes. Skip anonymous tokens and avoid recursive indexing. */
411
+ function indexSyntaxNodes(node: NamedSyntaxNode, index: Map<string, SyntaxNodeLike>): void {
412
+ const pending = [node];
413
+ while (pending.length > 0) {
414
+ const current = pending.pop();
415
+ if (!current) break;
416
+ index.set(syntaxNodeKey(current), current);
417
+ for (const child of current.namedChildren) pending.push(child);
418
+ }
419
+ }
420
+
421
+ function syntaxNodeKey(node: SyntaxNodeLike): string {
422
+ return `${node.type}:${node.startPosition.row}:${node.startPosition.column}:${node.endPosition.row}:${node.endPosition.column}`;
423
+ }
424
+
425
+ function captureNodeKey(capture: { nodeType: string; range: SourceRange }): string {
426
+ return `${capture.nodeType}:${capture.range.startLine - 1}:${capture.range.startCharacter - 1}:${capture.range.endLine - 1}:${capture.range.endCharacter - 1}`;
427
+ }
428
+
429
+ function compareSourceRanges(left: SourceRange, right: SourceRange): number {
430
+ return (
431
+ left.startLine - right.startLine ||
432
+ left.startCharacter - right.startCharacter ||
433
+ left.endLine - right.endLine ||
434
+ left.endCharacter - right.endCharacter
435
+ );
436
+ }
437
+
380
438
  /** Convert a tree-sitter node to a SourceRange using the source text for
381
439
  * UTF-16 column conversion. */
382
440
  function nodeToSourceRange(node: {
@@ -56,6 +56,8 @@ export interface CalleesAtResult {
56
56
  };
57
57
  callees: Array<{
58
58
  name: string;
59
+ /** Optional syntax-shortened label for presentation; does not replace `name`. */
60
+ displayName?: string;
59
61
  range: SourceRange;
60
62
  }>;
61
63
  depth: "direct" | "deep";
@@ -64,46 +64,47 @@ function plainValue(source, onError) {
64
64
  }
65
65
  if (badChar)
66
66
  onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`);
67
- return foldLines(source);
67
+ return unfoldLines(source);
68
68
  }
69
69
  function singleQuotedValue(source, onError) {
70
70
  if (source[source.length - 1] !== "'" || source.length === 1)
71
71
  onError(source.length, 'MISSING_CHAR', "Missing closing 'quote");
72
- return foldLines(source.slice(1, -1)).replace(/''/g, "'");
72
+ return unfoldLines(source.slice(1, -1)).replace(/''/g, "'");
73
73
  }
74
- function foldLines(source) {
74
+ function unfoldLines(source) {
75
+ const line = /(.*?)\r?\n/sy;
76
+ let match = line.exec(source);
77
+ if (!match)
78
+ return source;
75
79
  /**
76
- * The negative lookbehind here and in the `re` RegExp is to
80
+ * The negative lookbehinds in these RegExps are to
77
81
  * prevent causing a polynomial search time in certain cases.
78
82
  *
79
- * The try-catch is for Safari, which doesn't support this yet:
83
+ * The try-catch is for Safari < 16.4 and other old browsers:
80
84
  * https://caniuse.com/js-regexp-lookbehind
81
85
  */
82
- let first, line;
86
+ let trimEnd, trimBoth;
83
87
  try {
84
- first = new RegExp('(.*?)(?<![ \t])[ \t]*\r?\n', 'sy');
85
- line = new RegExp('[ \t]*(.*?)(?:(?<![ \t])[ \t]*)?\r?\n', 'sy');
88
+ trimEnd = new RegExp('(?<![ \t])[ \t]+$');
89
+ trimBoth = new RegExp('^[ \t]+|(?<![ \t])[ \t]+$', 'g');
86
90
  }
87
91
  catch {
88
- first = /(.*?)[ \t]*\r?\n/sy;
89
- line = /[ \t]*(.*?)[ \t]*\r?\n/sy;
92
+ trimEnd = /[ \t]+$/;
93
+ trimBoth = /^[ \t]+|[ \t]+$/g;
90
94
  }
91
- let match = first.exec(source);
92
- if (!match)
93
- return source;
94
- let res = match[1];
95
+ let res = match[1].replace(trimEnd, '');
95
96
  let sep = ' ';
96
- let pos = first.lastIndex;
97
- line.lastIndex = pos;
97
+ let pos = line.lastIndex;
98
98
  while ((match = line.exec(source))) {
99
- if (match[1] === '') {
99
+ const lm = match[1].replace(trimBoth, '');
100
+ if (lm === '') {
100
101
  if (sep === '\n')
101
102
  res += sep;
102
103
  else
103
104
  sep = '\n';
104
105
  }
105
106
  else {
106
- res += sep + match[1];
107
+ res += sep + lm;
107
108
  sep = ' ';
108
109
  }
109
110
  pos = line.lastIndex;
@@ -43,38 +43,40 @@ class Alias extends NodeBase {
43
43
  if (node.anchor === this.source)
44
44
  found = node;
45
45
  }
46
+ if (found && ctx) {
47
+ const { anchors, doc, maxAliasCount } = ctx;
48
+ let data = anchors.get(found);
49
+ if (!data) {
50
+ // Resolve anchors for Node.prototype.toJS()
51
+ toJS(found, null, ctx);
52
+ data = anchors.get(found);
53
+ }
54
+ /* istanbul ignore if */
55
+ if (data?.res === undefined) {
56
+ const msg = 'This should not happen: Alias anchor was not resolved?';
57
+ throw new ReferenceError(msg);
58
+ }
59
+ if (maxAliasCount >= 0) {
60
+ data.count += 1;
61
+ if (data.aliasCount === 0)
62
+ data.aliasCount = getAliasCount(doc, found, anchors);
63
+ if (data.count * data.aliasCount > maxAliasCount) {
64
+ const msg = 'Excessive alias count indicates a resource exhaustion attack';
65
+ throw new ReferenceError(msg);
66
+ }
67
+ }
68
+ }
46
69
  return found;
47
70
  }
48
71
  toJSON(_arg, ctx) {
49
72
  if (!ctx)
50
73
  return { source: this.source };
51
- const { anchors, doc, maxAliasCount } = ctx;
52
- const source = this.resolve(doc, ctx);
74
+ const source = this.resolve(ctx.doc, ctx);
53
75
  if (!source) {
54
76
  const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
55
77
  throw new ReferenceError(msg);
56
78
  }
57
- let data = anchors.get(source);
58
- if (!data) {
59
- // Resolve anchors for Node.prototype.toJS()
60
- toJS(source, null, ctx);
61
- data = anchors.get(source);
62
- }
63
- /* istanbul ignore if */
64
- if (data?.res === undefined) {
65
- const msg = 'This should not happen: Alias anchor was not resolved?';
66
- throw new ReferenceError(msg);
67
- }
68
- if (maxAliasCount >= 0) {
69
- data.count += 1;
70
- if (data.aliasCount === 0)
71
- data.aliasCount = getAliasCount(doc, source, anchors);
72
- if (data.count * data.aliasCount > maxAliasCount) {
73
- const msg = 'Excessive alias count indicates a resource exhaustion attack';
74
- throw new ReferenceError(msg);
75
- }
76
- }
77
- return data.res;
79
+ return ctx.anchors.get(source).res;
78
80
  }
79
81
  toString(ctx, _onComment, _onChompKeep) {
80
82
  const src = `*${this.source}`;
@@ -66,46 +66,47 @@ function plainValue(source, onError) {
66
66
  }
67
67
  if (badChar)
68
68
  onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`);
69
- return foldLines(source);
69
+ return unfoldLines(source);
70
70
  }
71
71
  function singleQuotedValue(source, onError) {
72
72
  if (source[source.length - 1] !== "'" || source.length === 1)
73
73
  onError(source.length, 'MISSING_CHAR', "Missing closing 'quote");
74
- return foldLines(source.slice(1, -1)).replace(/''/g, "'");
74
+ return unfoldLines(source.slice(1, -1)).replace(/''/g, "'");
75
75
  }
76
- function foldLines(source) {
76
+ function unfoldLines(source) {
77
+ const line = /(.*?)\r?\n/sy;
78
+ let match = line.exec(source);
79
+ if (!match)
80
+ return source;
77
81
  /**
78
- * The negative lookbehind here and in the `re` RegExp is to
82
+ * The negative lookbehinds in these RegExps are to
79
83
  * prevent causing a polynomial search time in certain cases.
80
84
  *
81
- * The try-catch is for Safari, which doesn't support this yet:
85
+ * The try-catch is for Safari < 16.4 and other old browsers:
82
86
  * https://caniuse.com/js-regexp-lookbehind
83
87
  */
84
- let first, line;
88
+ let trimEnd, trimBoth;
85
89
  try {
86
- first = new RegExp('(.*?)(?<![ \t])[ \t]*\r?\n', 'sy');
87
- line = new RegExp('[ \t]*(.*?)(?:(?<![ \t])[ \t]*)?\r?\n', 'sy');
90
+ trimEnd = new RegExp('(?<![ \t])[ \t]+$');
91
+ trimBoth = new RegExp('^[ \t]+|(?<![ \t])[ \t]+$', 'g');
88
92
  }
89
93
  catch {
90
- first = /(.*?)[ \t]*\r?\n/sy;
91
- line = /[ \t]*(.*?)[ \t]*\r?\n/sy;
94
+ trimEnd = /[ \t]+$/;
95
+ trimBoth = /^[ \t]+|[ \t]+$/g;
92
96
  }
93
- let match = first.exec(source);
94
- if (!match)
95
- return source;
96
- let res = match[1];
97
+ let res = match[1].replace(trimEnd, '');
97
98
  let sep = ' ';
98
- let pos = first.lastIndex;
99
- line.lastIndex = pos;
99
+ let pos = line.lastIndex;
100
100
  while ((match = line.exec(source))) {
101
- if (match[1] === '') {
101
+ const lm = match[1].replace(trimBoth, '');
102
+ if (lm === '') {
102
103
  if (sep === '\n')
103
104
  res += sep;
104
105
  else
105
106
  sep = '\n';
106
107
  }
107
108
  else {
108
- res += sep + match[1];
109
+ res += sep + lm;
109
110
  sep = ' ';
110
111
  }
111
112
  pos = line.lastIndex;
@@ -45,38 +45,40 @@ class Alias extends Node.NodeBase {
45
45
  if (node.anchor === this.source)
46
46
  found = node;
47
47
  }
48
+ if (found && ctx) {
49
+ const { anchors, doc, maxAliasCount } = ctx;
50
+ let data = anchors.get(found);
51
+ if (!data) {
52
+ // Resolve anchors for Node.prototype.toJS()
53
+ toJS.toJS(found, null, ctx);
54
+ data = anchors.get(found);
55
+ }
56
+ /* istanbul ignore if */
57
+ if (data?.res === undefined) {
58
+ const msg = 'This should not happen: Alias anchor was not resolved?';
59
+ throw new ReferenceError(msg);
60
+ }
61
+ if (maxAliasCount >= 0) {
62
+ data.count += 1;
63
+ if (data.aliasCount === 0)
64
+ data.aliasCount = getAliasCount(doc, found, anchors);
65
+ if (data.count * data.aliasCount > maxAliasCount) {
66
+ const msg = 'Excessive alias count indicates a resource exhaustion attack';
67
+ throw new ReferenceError(msg);
68
+ }
69
+ }
70
+ }
48
71
  return found;
49
72
  }
50
73
  toJSON(_arg, ctx) {
51
74
  if (!ctx)
52
75
  return { source: this.source };
53
- const { anchors, doc, maxAliasCount } = ctx;
54
- const source = this.resolve(doc, ctx);
76
+ const source = this.resolve(ctx.doc, ctx);
55
77
  if (!source) {
56
78
  const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
57
79
  throw new ReferenceError(msg);
58
80
  }
59
- let data = anchors.get(source);
60
- if (!data) {
61
- // Resolve anchors for Node.prototype.toJS()
62
- toJS.toJS(source, null, ctx);
63
- data = anchors.get(source);
64
- }
65
- /* istanbul ignore if */
66
- if (data?.res === undefined) {
67
- const msg = 'This should not happen: Alias anchor was not resolved?';
68
- throw new ReferenceError(msg);
69
- }
70
- if (maxAliasCount >= 0) {
71
- data.count += 1;
72
- if (data.aliasCount === 0)
73
- data.aliasCount = getAliasCount(doc, source, anchors);
74
- if (data.count * data.aliasCount > maxAliasCount) {
75
- const msg = 'Excessive alias count indicates a resource exhaustion attack';
76
- throw new ReferenceError(msg);
77
- }
78
- }
79
- return data.res;
81
+ return ctx.anchors.get(source).res;
80
82
  }
81
83
  toString(ctx, _onComment, _onChompKeep) {
82
84
  const src = `*${this.source}`;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaml",
3
- "version": "2.9.0",
3
+ "version": "2.9.1",
4
4
  "license": "ISC",
5
5
  "author": "Eemeli Aro <eemeli@gmail.com>",
6
6
  "funding": "https://github.com/sponsors/eemeli",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-intelligence",
3
- "version": "6.3.0",
3
+ "version": "7.0.0",
4
4
  "description": "LSP and AST navigation, search, diagnostics, and refactoring",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,10 +35,10 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "yaml": "^2.9.0",
38
- "@mrclrchtr/supi-code-runtime": "6.3.0",
39
- "@mrclrchtr/supi-core": "6.3.0",
40
- "@mrclrchtr/supi-tree-sitter": "6.3.0",
41
- "@mrclrchtr/supi-lsp": "6.3.0"
38
+ "@mrclrchtr/supi-code-runtime": "7.0.0",
39
+ "@mrclrchtr/supi-lsp": "7.0.0",
40
+ "@mrclrchtr/supi-core": "7.0.0",
41
+ "@mrclrchtr/supi-tree-sitter": "7.0.0"
42
42
  },
43
43
  "bundledDependencies": [
44
44
  "@mrclrchtr/supi-code-runtime",
@@ -312,8 +312,7 @@ function evidenceIsComplete(
312
312
  function diagnosticEvidenceReason(evidence: DiagnosticEvidenceSummary): string {
313
313
  let reason = `Diagnostic evidence is partial: ${evidence.requested} requested, ${evidence.confirmed} confirmed, ${evidence.unconfirmed} unconfirmed, ${evidence.failed} failed, ${evidence.removed} removed.`;
314
314
  if (evidence.unconfirmed > 0) {
315
- reason +=
316
- " Unconfirmed documents await a later diagnostic republish before their evidence can be confirmed (ADR 0021).";
315
+ reason += " Unconfirmed documents have no request-based confirmation.";
317
316
  }
318
317
  return reason;
319
318
  }
@@ -16,6 +16,7 @@ import {
16
16
  type DiagnosticEvidenceSummary,
17
17
  type FileScopeDecision,
18
18
  getFileScopeDecision,
19
+ isTsconfigApplicableFile,
19
20
  TENTATIVE_PUSH_UNAVAILABLE_REASON,
20
21
  type WorkspaceLspRuntime,
21
22
  } from "@mrclrchtr/supi-lsp/api";
@@ -26,7 +27,7 @@ import type {
26
27
  HealthDiagnosticScope,
27
28
  } from "../../session/health-types.ts";
28
29
 
29
- /** Collect a live per-file diagnostic pull with its tsconfig scope decision. */
30
+ /** Collect a live per-file diagnostic pull with an applicable config scope decision. */
30
31
  export async function collectScopedFileDiagnostics(options: {
31
32
  service: WorkspaceLspRuntime;
32
33
  scope: Extract<HealthDiagnosticScope, { kind: "file" }>;
@@ -87,6 +88,8 @@ export async function collectScopedFileDiagnostics(options: {
87
88
  * a failure to compute never fails the diagnostic request.
88
89
  */
89
90
  export function fileScopeStatus(filePath: string, cwd: string): FileScopeDecision | null {
91
+ if (!isTsconfigApplicableFile(filePath)) return null;
92
+
90
93
  try {
91
94
  return getFileScopeDecision(filePath, cwd);
92
95
  } catch {