@mrclrchtr/supi-code-intelligence 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +212 -0
  2. package/node_modules/@mrclrchtr/supi-core/README.md +90 -0
  3. package/node_modules/@mrclrchtr/supi-core/package.json +30 -0
  4. package/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
  5. package/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
  6. package/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
  7. package/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
  10. package/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
  11. package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
  12. package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
  13. package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
  14. package/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
  15. package/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
  16. package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
  17. package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
  18. package/node_modules/@mrclrchtr/supi-lsp/README.md +112 -0
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +90 -0
  20. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +30 -0
  21. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
  23. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
  24. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
  26. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
  27. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
  28. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
  29. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
  30. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
  31. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
  32. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
  34. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
  35. package/node_modules/@mrclrchtr/supi-lsp/package.json +45 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/src/capabilities.ts +62 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-refresh.ts +229 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +545 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +192 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/config.ts +143 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/defaults.json +82 -0
  42. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-augmentation.ts +82 -0
  43. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-display.ts +68 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +73 -0
  45. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostics.ts +98 -0
  46. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +47 -0
  47. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/suppression-diagnostics.ts +58 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +359 -0
  49. package/node_modules/@mrclrchtr/supi-lsp/src/guidance.ts +163 -0
  50. package/node_modules/@mrclrchtr/supi-lsp/src/index.ts +17 -0
  51. package/node_modules/@mrclrchtr/supi-lsp/src/lsp-state.ts +82 -0
  52. package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +470 -0
  53. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +34 -0
  54. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +139 -0
  55. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +39 -0
  56. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +46 -0
  57. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-types.ts +39 -0
  58. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +83 -0
  59. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +18 -0
  60. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +550 -0
  61. package/node_modules/@mrclrchtr/supi-lsp/src/overrides.ts +173 -0
  62. package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +197 -0
  63. package/node_modules/@mrclrchtr/supi-lsp/src/renderer.ts +120 -0
  64. package/node_modules/@mrclrchtr/supi-lsp/src/scanner.ts +153 -0
  65. package/node_modules/@mrclrchtr/supi-lsp/src/search-fallback.ts +98 -0
  66. package/node_modules/@mrclrchtr/supi-lsp/src/service-registry.ts +153 -0
  67. package/node_modules/@mrclrchtr/supi-lsp/src/settings-registration.ts +292 -0
  68. package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +153 -0
  69. package/node_modules/@mrclrchtr/supi-lsp/src/tool-actions.ts +430 -0
  70. package/node_modules/@mrclrchtr/supi-lsp/src/tree-persist.ts +48 -0
  71. package/node_modules/@mrclrchtr/supi-lsp/src/tsconfig-scope.ts +156 -0
  72. package/node_modules/@mrclrchtr/supi-lsp/src/types.ts +409 -0
  73. package/node_modules/@mrclrchtr/supi-lsp/src/ui.ts +358 -0
  74. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +122 -0
  75. package/node_modules/@mrclrchtr/supi-lsp/src/workspace-sentinels.ts +114 -0
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +97 -0
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +67 -0
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/.gitkeep +0 -0
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm +0 -0
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm.json +7 -0
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm +0 -0
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm.json +7 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm +0 -0
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm.json +7 -0
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm +0 -0
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm.json +7 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm +0 -0
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm.json +7 -0
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm +0 -0
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm.json +7 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm +0 -0
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm.json +7 -0
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +12 -0
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm +0 -0
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm.json +7 -0
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm +0 -0
  98. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm.json +7 -0
  99. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm +0 -0
  100. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm.json +7 -0
  101. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm +0 -0
  102. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm.json +7 -0
  103. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
  104. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +19 -0
  105. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm +0 -0
  106. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm.json +7 -0
  107. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm +0 -0
  108. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm.json +7 -0
  109. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +126 -0
  110. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +144 -0
  111. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +151 -0
  112. package/node_modules/@mrclrchtr/supi-tree-sitter/src/callees.ts +343 -0
  113. package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +108 -0
  114. package/node_modules/@mrclrchtr/supi-tree-sitter/src/exports.ts +315 -0
  115. package/node_modules/@mrclrchtr/supi-tree-sitter/src/formatting.ts +104 -0
  116. package/node_modules/@mrclrchtr/supi-tree-sitter/src/imports.ts +42 -0
  117. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +16 -0
  118. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +116 -0
  119. package/node_modules/@mrclrchtr/supi-tree-sitter/src/node-at.ts +96 -0
  120. package/node_modules/@mrclrchtr/supi-tree-sitter/src/outline.ts +287 -0
  121. package/node_modules/@mrclrchtr/supi-tree-sitter/src/runtime.ts +237 -0
  122. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session.ts +112 -0
  123. package/node_modules/@mrclrchtr/supi-tree-sitter/src/structure.ts +7 -0
  124. package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +13 -0
  125. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +306 -0
  126. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +146 -0
  127. package/package.json +47 -0
  128. package/src/actions/affected-action.ts +310 -0
  129. package/src/actions/brief-action.ts +242 -0
  130. package/src/actions/callees-action.ts +134 -0
  131. package/src/actions/callers-action.ts +215 -0
  132. package/src/actions/implementations-action.ts +190 -0
  133. package/src/actions/index-action.ts +187 -0
  134. package/src/actions/pattern-action.ts +232 -0
  135. package/src/architecture.ts +367 -0
  136. package/src/brief-focused.ts +383 -0
  137. package/src/brief.ts +228 -0
  138. package/src/code-intelligence.ts +122 -0
  139. package/src/git-context.ts +65 -0
  140. package/src/guidance.ts +39 -0
  141. package/src/index.ts +28 -0
  142. package/src/resolve-target.ts +104 -0
  143. package/src/search-helpers.ts +283 -0
  144. package/src/target-resolution.ts +368 -0
  145. package/src/tool-actions.ts +109 -0
  146. package/src/types.ts +57 -0
@@ -0,0 +1,368 @@
1
+ // Target resolution — resolve symbol references to concrete file positions
2
+ // for semantic actions (callers, callees, implementations, affected).
3
+
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { isWithinOrEqual } from "@mrclrchtr/supi-core";
7
+ import { getSessionLspService, type Position, type SessionLspService } from "@mrclrchtr/supi-lsp";
8
+ import { escapeRegex, normalizePath } from "./search-helpers.ts";
9
+ import type { ConfidenceMode, DisambiguationCandidate } from "./types.ts";
10
+
11
+ export interface ResolvedTarget {
12
+ file: string;
13
+ /** 0-based position for LSP API */
14
+ position: Position;
15
+ /** 1-based position for user display */
16
+ displayLine: number;
17
+ displayCharacter: number;
18
+ name: string | null;
19
+ kind: string | null;
20
+ confidence: ConfidenceMode;
21
+ }
22
+
23
+ export type TargetResolutionResult =
24
+ | { kind: "resolved"; target: ResolvedTarget }
25
+ | { kind: "disambiguation"; candidates: DisambiguationCandidate[]; omittedCount: number }
26
+ | { kind: "error"; message: string };
27
+
28
+ // Re-export normalizePath for consumers who import from target-resolution
29
+ export { normalizePath } from "./search-helpers.ts";
30
+
31
+ /**
32
+ * Convert 1-based public coordinates to 0-based LSP Position.
33
+ */
34
+ export function toZeroBased(line: number, character: number): Position {
35
+ return { line: line - 1, character: character - 1 };
36
+ }
37
+
38
+ /**
39
+ * Resolve a target from anchored coordinates (file + line + character).
40
+ */
41
+ export function resolveAnchoredTarget(
42
+ file: string,
43
+ line: number,
44
+ character: number,
45
+ cwd: string,
46
+ ): TargetResolutionResult {
47
+ const resolvedFile = normalizePath(file, cwd);
48
+
49
+ if (!fs.existsSync(resolvedFile)) {
50
+ return { kind: "error", message: `File not found: \`${file}\`` };
51
+ }
52
+
53
+ if (isBinaryFile(resolvedFile)) {
54
+ return {
55
+ kind: "error",
56
+ message: `File type not supported for semantic analysis: \`${file}\`. Try \`code_intel pattern\` for text search.`,
57
+ };
58
+ }
59
+
60
+ const position = toZeroBased(line, character);
61
+
62
+ return {
63
+ kind: "resolved",
64
+ target: {
65
+ file: resolvedFile,
66
+ position,
67
+ displayLine: line,
68
+ displayCharacter: character,
69
+ name: null,
70
+ kind: null,
71
+ confidence: "semantic",
72
+ },
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Resolve a target from symbol discovery — finds matching declarations.
78
+ * Uses LSP workspace symbols when available, falls back to Tree-sitter/text search.
79
+ */
80
+ export async function resolveSymbolTarget(
81
+ symbol: string,
82
+ cwd: string,
83
+ options?: {
84
+ path?: string;
85
+ kind?: string;
86
+ exportedOnly?: boolean;
87
+ },
88
+ ): Promise<TargetResolutionResult> {
89
+ const lspState = getSessionLspService(cwd);
90
+
91
+ if (lspState.kind === "ready") {
92
+ return resolveSymbolViaLsp(symbol, cwd, lspState.service, options);
93
+ }
94
+
95
+ if (lspState.kind === "pending") {
96
+ // In v1, we may wait for LSP. For now, try structural fallback.
97
+ }
98
+
99
+ // Structural fallback via text search
100
+ return resolveSymbolViaSearch(symbol, cwd, options);
101
+ }
102
+
103
+ async function resolveSymbolViaLsp(
104
+ symbol: string,
105
+ cwd: string,
106
+ lsp: SessionLspService,
107
+ options?: { path?: string; kind?: string; exportedOnly?: boolean },
108
+ ): Promise<TargetResolutionResult> {
109
+ const results = await lsp.workspaceSymbol(symbol);
110
+ if (!results || results.length === 0) {
111
+ return { kind: "error", message: `Symbol not found: \`${symbol}\`` };
112
+ }
113
+
114
+ // Filter by path scope
115
+ const scopePath = options?.path ? normalizePath(options.path, cwd) : null;
116
+ let candidates = results.filter((s) => {
117
+ if (!("location" in s) || !s.location) return false;
118
+ const uri = s.location.uri;
119
+ const filePath = uri.startsWith("file://") ? decodeURIComponent(uri.slice(7)) : uri;
120
+ if (scopePath && !isWithinOrEqual(scopePath, filePath)) return false;
121
+ return true;
122
+ });
123
+
124
+ // Filter by kind
125
+ if (options?.kind) {
126
+ const kindLower = options.kind.toLowerCase();
127
+ candidates = candidates.filter((s) => {
128
+ const symbolKind = symbolKindName(s.kind);
129
+ return symbolKind.toLowerCase().includes(kindLower);
130
+ });
131
+ }
132
+
133
+ // Filter to exported symbols only (heuristic: non-local SymbolKinds)
134
+ if (options?.exportedOnly) {
135
+ candidates = candidates.filter((s) => {
136
+ // LSP workspace symbols don't expose export visibility directly.
137
+ // Filter out SymbolKinds that are typically local/private (Variable, Field, Property).
138
+ const NON_EXPORTED_KINDS = new Set([7, 8, 13]); // Property, Field, Variable
139
+ return !NON_EXPORTED_KINDS.has(s.kind);
140
+ });
141
+ }
142
+
143
+ if (candidates.length === 0) {
144
+ return {
145
+ kind: "error",
146
+ message: `Symbol not found: \`${symbol}\`${scopePath ? ` in path \`${options?.path}\`` : ""}`,
147
+ };
148
+ }
149
+
150
+ if (candidates.length === 1) {
151
+ const c = candidates[0];
152
+ const loc = "location" in c ? c.location : null;
153
+ if (!loc) {
154
+ return { kind: "error", message: `Symbol not found: \`${symbol}\`` };
155
+ }
156
+ const filePath = loc.uri.startsWith("file://") ? decodeURIComponent(loc.uri.slice(7)) : loc.uri;
157
+
158
+ return {
159
+ kind: "resolved",
160
+ target: {
161
+ file: filePath,
162
+ position: loc.range.start,
163
+ displayLine: loc.range.start.line + 1,
164
+ displayCharacter: loc.range.start.character + 1,
165
+ name: c.name,
166
+ kind: symbolKindName(c.kind),
167
+ confidence: "semantic",
168
+ },
169
+ };
170
+ }
171
+
172
+ // Multiple candidates — return disambiguation
173
+ const MAX_CANDIDATES = 8;
174
+ const disambiguated = candidates
175
+ .slice(0, MAX_CANDIDATES)
176
+ .map((c, idx) => mapCandidateToDisambiguation(c, idx, cwd));
177
+
178
+ return {
179
+ kind: "disambiguation",
180
+ candidates: disambiguated,
181
+ omittedCount: Math.max(0, candidates.length - MAX_CANDIDATES),
182
+ };
183
+ }
184
+
185
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: ripgrep-based symbol discovery with pattern parsing
186
+ async function resolveSymbolViaSearch(
187
+ symbol: string,
188
+ cwd: string,
189
+ options?: { path?: string; kind?: string; exportedOnly?: boolean },
190
+ ): Promise<TargetResolutionResult> {
191
+ const { execFileSync } = await import("node:child_process");
192
+ const scopePath = options?.path ? normalizePath(options.path, cwd) : cwd;
193
+
194
+ try {
195
+ const exportOnly = options?.exportedOnly;
196
+ const pattern = exportOnly
197
+ ? `export\\s+(function|class|interface|type|const|let|var)\\s+${escapeRegex(symbol)}\\b`
198
+ : `(function|class|interface|type|const|let|var|export)\\s+${escapeRegex(symbol)}\\b`;
199
+ let output: string;
200
+ try {
201
+ output = execFileSync("rg", ["--json", "-m", "10", "-e", pattern, scopePath], {
202
+ encoding: "utf-8",
203
+ cwd,
204
+ timeout: 5000,
205
+ stdio: ["pipe", "pipe", "pipe"],
206
+ });
207
+ } catch (err: unknown) {
208
+ // rg exits 1 for no-match; capture stdout if available
209
+ const e = err as { status?: number; stdout?: string };
210
+ output = e.stdout ?? "";
211
+ }
212
+
213
+ const matches: Array<{ file: string; line: number; text: string }> = [];
214
+ for (const line of output.split("\n")) {
215
+ if (!line.trim()) continue;
216
+ try {
217
+ const parsed = JSON.parse(line);
218
+ if (parsed.type === "match" && parsed.data) {
219
+ const filePath = parsed.data.path?.text;
220
+ const lineNum = parsed.data.line_number;
221
+ const text = parsed.data.lines?.text?.trim();
222
+ if (filePath && lineNum) {
223
+ matches.push({ file: filePath, line: lineNum, text: text ?? "" });
224
+ }
225
+ }
226
+ } catch {
227
+ // Skip malformed JSON lines
228
+ }
229
+ }
230
+
231
+ if (matches.length === 0) {
232
+ return { kind: "error", message: `Symbol not found: \`${symbol}\`` };
233
+ }
234
+
235
+ if (matches.length === 1) {
236
+ const m = matches[0];
237
+ const resolvedFile = path.resolve(cwd, m.file);
238
+ return {
239
+ kind: "resolved",
240
+ target: {
241
+ file: resolvedFile,
242
+ position: { line: m.line - 1, character: 0 },
243
+ displayLine: m.line,
244
+ displayCharacter: 1,
245
+ name: symbol,
246
+ kind: null,
247
+ confidence: "heuristic",
248
+ },
249
+ };
250
+ }
251
+
252
+ // Multiple matches — disambiguation
253
+ const disambiguated: DisambiguationCandidate[] = matches.slice(0, 8).map((m, idx) => ({
254
+ name: symbol,
255
+ kind: null,
256
+ container: null,
257
+ file: m.file,
258
+ line: m.line,
259
+ character: 1,
260
+ reason: m.text.slice(0, 80),
261
+ rank: idx + 1,
262
+ }));
263
+
264
+ return {
265
+ kind: "disambiguation",
266
+ candidates: disambiguated,
267
+ omittedCount: Math.max(0, matches.length - 8),
268
+ };
269
+ } catch {
270
+ return { kind: "error", message: `Symbol not found: \`${symbol}\`` };
271
+ }
272
+ }
273
+
274
+ // ── Helpers ───────────────────────────────────────────────────────────
275
+
276
+ function mapCandidateToDisambiguation(
277
+ c: {
278
+ name: string;
279
+ kind: number;
280
+ containerName?: string | null;
281
+ location?: { uri: string; range: { start: { line: number; character: number } } } | null;
282
+ },
283
+ idx: number,
284
+ cwd: string,
285
+ ): DisambiguationCandidate {
286
+ const loc = "location" in c ? c.location : null;
287
+ const filePath = loc
288
+ ? loc.uri.startsWith("file://")
289
+ ? decodeURIComponent(loc.uri.slice(7))
290
+ : loc.uri
291
+ : "";
292
+ const relPath = filePath ? path.relative(cwd, filePath) : "";
293
+
294
+ return {
295
+ name: c.name,
296
+ kind: symbolKindName(c.kind),
297
+ container: "containerName" in c ? (c.containerName ?? null) : null,
298
+ file: relPath,
299
+ line: loc ? loc.range.start.line + 1 : 0,
300
+ character: loc ? loc.range.start.character + 1 : 0,
301
+ reason: relPath,
302
+ rank: idx + 1,
303
+ };
304
+ }
305
+
306
+ const BINARY_EXTENSIONS = new Set([
307
+ ".png",
308
+ ".jpg",
309
+ ".jpeg",
310
+ ".gif",
311
+ ".webp",
312
+ ".bmp",
313
+ ".ico",
314
+ ".woff",
315
+ ".woff2",
316
+ ".ttf",
317
+ ".eot",
318
+ ".zip",
319
+ ".tar",
320
+ ".gz",
321
+ ".bz2",
322
+ ".pdf",
323
+ ".doc",
324
+ ".docx",
325
+ ".exe",
326
+ ".dll",
327
+ ".so",
328
+ ".dylib",
329
+ ".wasm",
330
+ ".node",
331
+ ]);
332
+
333
+ function isBinaryFile(filePath: string): boolean {
334
+ return BINARY_EXTENSIONS.has(path.extname(filePath).toLowerCase());
335
+ }
336
+
337
+ /** Map LSP SymbolKind to a human-readable name. */
338
+ function symbolKindName(kind: number): string {
339
+ const kinds: Record<number, string> = {
340
+ 1: "File",
341
+ 2: "Module",
342
+ 3: "Namespace",
343
+ 4: "Package",
344
+ 5: "Class",
345
+ 6: "Method",
346
+ 7: "Property",
347
+ 8: "Field",
348
+ 9: "Constructor",
349
+ 10: "Enum",
350
+ 11: "Interface",
351
+ 12: "Function",
352
+ 13: "Variable",
353
+ 14: "Constant",
354
+ 15: "String",
355
+ 16: "Number",
356
+ 17: "Boolean",
357
+ 18: "Array",
358
+ 19: "Object",
359
+ 20: "Key",
360
+ 21: "Null",
361
+ 22: "EnumMember",
362
+ 23: "Struct",
363
+ 24: "Event",
364
+ 25: "Operator",
365
+ 26: "TypeParameter",
366
+ };
367
+ return kinds[kind] ?? "Unknown";
368
+ }
@@ -0,0 +1,109 @@
1
+ // Tool action router — dispatches code_intel actions to specific implementations.
2
+
3
+ import * as fs from "node:fs";
4
+ import { executeAffectedAction } from "./actions/affected-action.ts";
5
+ import { executeBriefAction } from "./actions/brief-action.ts";
6
+ import { executeCalleesAction } from "./actions/callees-action.ts";
7
+ import { executeCallersAction } from "./actions/callers-action.ts";
8
+ import { executeImplementationsAction } from "./actions/implementations-action.ts";
9
+ import { executeIndexAction } from "./actions/index-action.ts";
10
+ import { executePatternAction } from "./actions/pattern-action.ts";
11
+ import { normalizePath } from "./search-helpers.ts";
12
+ import type { CodeIntelResult } from "./types.ts";
13
+
14
+ export type CodeIntelAction =
15
+ | "brief"
16
+ | "callers"
17
+ | "callees"
18
+ | "implementations"
19
+ | "affected"
20
+ | "pattern"
21
+ | "index";
22
+
23
+ /** Flat parameter bag shared by `code_intel` action handlers. */
24
+ export interface ActionParams {
25
+ action: CodeIntelAction;
26
+ path?: string;
27
+ file?: string;
28
+ line?: number;
29
+ character?: number;
30
+ symbol?: string;
31
+ /** Text search input for `action: "pattern"`; treated as literal unless `regex` is true. */
32
+ pattern?: string;
33
+ /** Opt into raw ripgrep regex semantics for `action: "pattern"`. */
34
+ regex?: boolean;
35
+ kind?: string;
36
+ exportedOnly?: boolean;
37
+ maxResults?: number;
38
+ contextLines?: number;
39
+ /** Aggregate counts by directory instead of line-level matches (pattern action only). */
40
+ summary?: boolean;
41
+ }
42
+
43
+ const SUPPORTED_ACTIONS = new Set<string>([
44
+ "brief",
45
+ "callers",
46
+ "callees",
47
+ "implementations",
48
+ "affected",
49
+ "pattern",
50
+ "index",
51
+ ]);
52
+
53
+ /**
54
+ * Main action dispatcher — validates params and routes to specific action handlers.
55
+ * Returns structured content with optional metadata details per action type.
56
+ */
57
+ export async function executeAction(
58
+ params: ActionParams,
59
+ ctx: { cwd: string },
60
+ ): Promise<CodeIntelResult> {
61
+ const cwd = ctx.cwd;
62
+ const error = validateParams(params, cwd);
63
+ if (error) return { content: error, details: undefined };
64
+
65
+ switch (params.action) {
66
+ case "brief":
67
+ return executeBriefAction(params, cwd);
68
+ case "callers":
69
+ return executeCallersAction(params, cwd);
70
+ case "callees":
71
+ return executeCalleesAction(params, cwd);
72
+ case "implementations":
73
+ return executeImplementationsAction(params, cwd);
74
+ case "affected":
75
+ return executeAffectedAction(params, cwd);
76
+ case "pattern":
77
+ return executePatternAction(params, cwd);
78
+ case "index":
79
+ return executeIndexAction(cwd);
80
+ default:
81
+ return {
82
+ content: `**Error:** Unknown action \`${params.action}\`.`,
83
+ details: undefined,
84
+ };
85
+ }
86
+ }
87
+
88
+ function validateParams(params: ActionParams, cwd: string): string | null {
89
+ if (!params.action || !SUPPORTED_ACTIONS.has(params.action)) {
90
+ return `**Error:** Unknown action \`${params.action ?? "(none)"}\`. Supported: \`brief\`, \`callers\`, \`callees\`, \`implementations\`, \`affected\`, \`pattern\`, \`index\`.`;
91
+ }
92
+
93
+ if (params.path && (params.line != null || params.character != null)) {
94
+ return "**Error:** `line` and `character` require `file`, not `path`. Use `path` to scope/focus; use `file` to anchor a position.";
95
+ }
96
+
97
+ if (params.file) {
98
+ const resolvedFile = normalizePath(params.file, cwd);
99
+ if (fs.existsSync(resolvedFile) && fs.statSync(resolvedFile).isDirectory()) {
100
+ return "**Error:** `file` points to a directory. Use `path` to scope a directory; use `file` to anchor a position in a file.";
101
+ }
102
+ }
103
+
104
+ if ((params.line != null || params.character != null) && !params.file) {
105
+ return "**Error:** `line` and `character` require `file`.";
106
+ }
107
+
108
+ return null;
109
+ }
package/src/types.ts ADDED
@@ -0,0 +1,57 @@
1
+ // Shared types for code intelligence tool results and metadata.
2
+
3
+ /** Confidence vocabulary for result labeling. */
4
+ export type ConfidenceMode = "semantic" | "structural" | "heuristic" | "unavailable";
5
+
6
+ /** Structured details metadata returned alongside markdown brief content. */
7
+ export interface BriefDetails {
8
+ confidence: ConfidenceMode;
9
+ focusTarget: string | null;
10
+ startHere: Array<{ target: string; reason: string }>;
11
+ publicSurfaces: string[];
12
+ dependencySummary: { moduleCount: number; edgeCount: number } | null;
13
+ omittedCount: number;
14
+ nextQueries: string[];
15
+ }
16
+
17
+ /** Structured details metadata for relationship and pattern results. */
18
+ export interface SearchDetails {
19
+ confidence: ConfidenceMode;
20
+ scope: string | null;
21
+ candidateCount: number;
22
+ omittedCount: number;
23
+ nextQueries: string[];
24
+ }
25
+
26
+ /** Structured details metadata for affected analysis results. */
27
+ export interface AffectedDetails {
28
+ confidence: ConfidenceMode;
29
+ directCount: number;
30
+ downstreamCount: number;
31
+ riskLevel: "low" | "medium" | "high";
32
+ checkNext: string[];
33
+ likelyTests: string[];
34
+ omittedCount: number;
35
+ nextQueries: string[];
36
+ }
37
+
38
+ /** Disambiguation candidate for ambiguous symbol resolution. */
39
+ export interface DisambiguationCandidate {
40
+ name: string;
41
+ kind: string | null;
42
+ container: string | null;
43
+ file: string;
44
+ line: number;
45
+ character: number;
46
+ reason: string;
47
+ rank: number;
48
+ }
49
+
50
+ /** Tool result shape returned by executeAction. */
51
+ export interface CodeIntelResult {
52
+ content: string;
53
+ details?:
54
+ | { type: "brief"; data: BriefDetails }
55
+ | { type: "search"; data: SearchDetails }
56
+ | { type: "affected"; data: AffectedDetails };
57
+ }