@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,215 @@
1
+ // Callers action — find call sites for a symbol.
2
+
3
+ import * as path from "node:path";
4
+ import { getSessionLspService } from "@mrclrchtr/supi-lsp";
5
+ import { resolveTarget } from "../resolve-target.ts";
6
+ import {
7
+ escapeRegex,
8
+ filterOutDeclaration,
9
+ groupByFile,
10
+ isInProjectPath,
11
+ normalizePath,
12
+ runRipgrep,
13
+ uriToFile,
14
+ } from "../search-helpers.ts";
15
+ import type { ActionParams } from "../tool-actions.ts";
16
+ import type { CodeIntelResult, SearchDetails } from "../types.ts";
17
+
18
+ export async function executeCallersAction(
19
+ params: ActionParams,
20
+ cwd: string,
21
+ ): Promise<CodeIntelResult> {
22
+ const target = await resolveTarget(params, cwd);
23
+ if (typeof target === "string") {
24
+ return {
25
+ content: target,
26
+ details: {
27
+ type: "search" as const,
28
+ data: {
29
+ confidence: "unavailable",
30
+ scope: null,
31
+ candidateCount: 0,
32
+ omittedCount: 0,
33
+ nextQueries: ["Provide `file`, `line`, `character` or a `symbol` to resolve the target"],
34
+ },
35
+ },
36
+ };
37
+ }
38
+
39
+ const maxResults = params.maxResults ?? 5;
40
+ const lspState = getSessionLspService(cwd);
41
+
42
+ if (lspState.kind === "ready") {
43
+ const refs = await lspState.service.references(target.file, target.position);
44
+ if (refs && refs.length > 0) {
45
+ // Filter out the declaration itself — LSP includes it with includeDeclaration
46
+ const callerRefs = filterOutDeclaration(refs, target.file, target.position);
47
+ if (callerRefs.length > 0) {
48
+ const content = formatSemanticCallers(callerRefs, target.name, cwd, maxResults);
49
+ const { project: projectRefs, external: externalRefs } = partitionRefs(refs, cwd);
50
+ const details: SearchDetails = {
51
+ confidence: "semantic",
52
+ scope: params.path ?? null,
53
+ candidateCount: projectRefs.length,
54
+ omittedCount: externalRefs.length,
55
+ nextQueries: [
56
+ "`code_intel affected` for impact analysis",
57
+ "`code_intel pattern` with broader scope for additional matches",
58
+ ],
59
+ };
60
+ return { content, details: { type: "search" as const, data: details } };
61
+ }
62
+ }
63
+ }
64
+
65
+ if (target.name) {
66
+ const result = formatHeuristicCallers(target.name, params, cwd);
67
+ const details: SearchDetails = {
68
+ confidence: "heuristic",
69
+ scope: params.path ?? null,
70
+ candidateCount: result.matchCount,
71
+ omittedCount: 0,
72
+ nextQueries: ["Enable LSP for `semantic` caller accuracy"],
73
+ };
74
+ return { content: result.content, details: { type: "search" as const, data: details } };
75
+ }
76
+
77
+ const relPath = path.relative(cwd, target.file);
78
+ return {
79
+ content: `No caller data available for ${relPath}:${target.displayLine}:${target.displayCharacter}. LSP may not be active.\n\nTry \`code_intel pattern\` with the symbol name for text-search matches.`,
80
+ details: {
81
+ type: "search" as const,
82
+ data: {
83
+ confidence: "unavailable",
84
+ scope: params.path ?? null,
85
+ candidateCount: 0,
86
+ omittedCount: 0,
87
+ nextQueries: ["Enable LSP for semantic caller resolution, or try `code_intel pattern`"],
88
+ },
89
+ },
90
+ };
91
+ }
92
+
93
+ function partitionRefs(
94
+ refs: Array<{ uri: string; range: { start: { line: number; character: number } } }>,
95
+ cwd: string,
96
+ ): { project: typeof refs; external: typeof refs } {
97
+ const project: typeof refs = [];
98
+ const external: typeof refs = [];
99
+ for (const ref of refs) {
100
+ if (isInProjectPath(uriToFile(ref.uri), cwd)) {
101
+ project.push(ref);
102
+ } else {
103
+ external.push(ref);
104
+ }
105
+ }
106
+ return { project, external };
107
+ }
108
+
109
+ function groupRefsByFile(
110
+ refs: Array<{ uri: string; range: { start: { line: number; character: number } } }>,
111
+ cwd: string,
112
+ ): Map<string, number[]> {
113
+ const byFile = new Map<string, number[]>();
114
+ for (const ref of refs) {
115
+ const filePath = uriToFile(ref.uri);
116
+ const relPath = path.relative(cwd, filePath);
117
+ const group = byFile.get(relPath) ?? [];
118
+ group.push(ref.range.start.line + 1);
119
+ byFile.set(relPath, group);
120
+ }
121
+ return byFile;
122
+ }
123
+
124
+ function formatSemanticCallers(
125
+ refs: Array<{ uri: string; range: { start: { line: number; character: number } } }>,
126
+ name: string | null,
127
+ cwd: string,
128
+ maxResults: number,
129
+ ): string {
130
+ const { project: projectRefs, external: externalRefs } = partitionRefs(refs, cwd);
131
+
132
+ const lines: string[] = [];
133
+ lines.push(`# Callers of \`${name ?? "symbol"}\``);
134
+ lines.push("");
135
+ lines.push(
136
+ `**${projectRefs.length} reference${projectRefs.length !== 1 ? "s" : ""}** (semantic)`,
137
+ );
138
+ if (externalRefs.length > 0) {
139
+ const suffix =
140
+ externalRefs.length === 1
141
+ ? "+1 external reference"
142
+ : `+${externalRefs.length} external references`;
143
+ lines.push(`_${suffix} (node_modules, .pnpm, or out-of-tree)_`);
144
+ }
145
+ lines.push("");
146
+
147
+ const byFile = groupRefsByFile(projectRefs, cwd);
148
+
149
+ let shown = 0;
150
+ for (const [file, locations] of byFile) {
151
+ if (shown >= maxResults) break;
152
+ lines.push(`### ${file}`);
153
+ for (const loc of locations.slice(0, 5)) {
154
+ lines.push(`- L${loc}`);
155
+ }
156
+ if (locations.length > 5) {
157
+ lines.push(`- _+${locations.length - 5} more in this file_`);
158
+ }
159
+ lines.push("");
160
+ shown++;
161
+ }
162
+
163
+ if (byFile.size > maxResults) {
164
+ lines.push(
165
+ `_+${byFile.size - maxResults} more files omitted. Narrow with \`path\` or increase \`maxResults\`._`,
166
+ );
167
+ lines.push("");
168
+ }
169
+
170
+ return lines.join("\n");
171
+ }
172
+
173
+ function formatHeuristicCallers(
174
+ symbol: string,
175
+ params: ActionParams,
176
+ cwd: string,
177
+ ): { content: string; matchCount: number } {
178
+ const maxResults = params.maxResults ?? 8;
179
+ const scopePath = params.path ? normalizePath(params.path, cwd) : cwd;
180
+ const pattern = `\\b${escapeRegex(symbol)}\\b`;
181
+ const matches = runRipgrep(pattern, scopePath, cwd, { maxMatches: maxResults * 3 });
182
+
183
+ if (matches.length === 0) {
184
+ return { content: `No references found for \`${symbol}\` (heuristic).`, matchCount: 0 };
185
+ }
186
+
187
+ const lines: string[] = [];
188
+ lines.push(`# Callers of \`${symbol}\` (heuristic)`);
189
+ lines.push("");
190
+ lines.push(
191
+ `**${matches.length} match${matches.length > 1 ? "es" : ""}** — text-search hints, not semantic references`,
192
+ );
193
+ lines.push("");
194
+
195
+ const byFile = groupByFile(matches);
196
+ let shown = 0;
197
+ for (const [file, fileMatches] of byFile) {
198
+ if (shown >= maxResults) break;
199
+ lines.push(`### ${file}`);
200
+ for (const m of fileMatches.slice(0, 3)) {
201
+ lines.push(`- L${m.line}: \`${m.text.slice(0, 100)}\``);
202
+ }
203
+ if (fileMatches.length > 3) {
204
+ lines.push(`- _+${fileMatches.length - 3} more_`);
205
+ }
206
+ lines.push("");
207
+ shown++;
208
+ }
209
+
210
+ if (byFile.size > maxResults) {
211
+ lines.push(`_+${byFile.size - maxResults} more files omitted._`);
212
+ }
213
+
214
+ return { content: lines.join("\n"), matchCount: matches.length };
215
+ }
@@ -0,0 +1,190 @@
1
+ // Implementations action — find concrete implementations via LSP or heuristic.
2
+
3
+ import * as path from "node:path";
4
+ import { getSessionLspService } from "@mrclrchtr/supi-lsp";
5
+ import { resolveTarget } from "../resolve-target.ts";
6
+ import {
7
+ escapeRegex,
8
+ isInProjectPath,
9
+ normalizePath,
10
+ runRipgrep,
11
+ uriToFile,
12
+ } from "../search-helpers.ts";
13
+ import type { ActionParams } from "../tool-actions.ts";
14
+ import type { CodeIntelResult, SearchDetails } from "../types.ts";
15
+
16
+ export async function executeImplementationsAction(
17
+ params: ActionParams,
18
+ cwd: string,
19
+ ): Promise<CodeIntelResult> {
20
+ const target = await resolveTarget(params, cwd);
21
+ if (typeof target === "string") {
22
+ return {
23
+ content: target,
24
+ details: {
25
+ type: "search" as const,
26
+ data: {
27
+ confidence: "unavailable",
28
+ scope: null,
29
+ candidateCount: 0,
30
+ omittedCount: 0,
31
+ nextQueries: ["Provide `file`, `line`, `character` or a `symbol` to resolve the target"],
32
+ },
33
+ },
34
+ };
35
+ }
36
+
37
+ const lspState = getSessionLspService(cwd);
38
+ const relPath = path.relative(cwd, target.file);
39
+
40
+ if (lspState.kind === "ready") {
41
+ const impls = await lspState.service.implementation(target.file, target.position);
42
+ if (impls) {
43
+ const locations = Array.isArray(impls) ? impls : [impls];
44
+ if (locations.length > 0) {
45
+ const content = formatSemanticImpls(locations, cwd, params.maxResults ?? 8);
46
+ const { project: projectLocs, external: externalLocs } = partitionImpls(locations, cwd);
47
+ const searchDetails: SearchDetails = {
48
+ confidence: "semantic",
49
+ scope: params.path ?? null,
50
+ candidateCount: projectLocs.length,
51
+ omittedCount: externalLocs.length,
52
+ nextQueries: [
53
+ "`code_intel affected` before changing implementations",
54
+ "`code_intel brief` on containing modules for deeper context",
55
+ ],
56
+ };
57
+ return { content, details: { type: "search" as const, data: searchDetails } };
58
+ }
59
+ }
60
+ }
61
+
62
+ if (target.name) {
63
+ const result = formatHeuristicImpls(target.name, params, cwd);
64
+ const details: SearchDetails = {
65
+ confidence: "heuristic",
66
+ scope: params.path ?? null,
67
+ candidateCount: result.matchCount,
68
+ omittedCount: 0,
69
+ nextQueries: ["Enable LSP for semantic implementation resolution"],
70
+ };
71
+ return { content: result.content, details: { type: "search" as const, data: details } };
72
+ }
73
+
74
+ return {
75
+ content: `No implementations found for ${relPath}:${target.displayLine}:${target.displayCharacter}.\n\nLSP implementation lookup may not be available. Try \`code_intel pattern\` with the type name.`,
76
+ details: {
77
+ type: "search" as const,
78
+ data: {
79
+ confidence: "unavailable",
80
+ scope: params.path ?? null,
81
+ candidateCount: 0,
82
+ omittedCount: 0,
83
+ nextQueries: [
84
+ "Enable LSP for semantic implementation resolution, or try `code_intel pattern`",
85
+ ],
86
+ },
87
+ },
88
+ };
89
+ }
90
+
91
+ function partitionImpls(
92
+ locations: Array<{
93
+ uri?: string;
94
+ targetUri?: string;
95
+ range?: { start: { line: number } };
96
+ targetRange?: { start: { line: number } };
97
+ }>,
98
+ cwd: string,
99
+ ): { project: typeof locations; external: typeof locations } {
100
+ const project: typeof locations = [];
101
+ const external: typeof locations = [];
102
+ for (const loc of locations) {
103
+ const uri = loc.uri ?? loc.targetUri ?? "";
104
+ const filePath = uriToFile(uri);
105
+ if (filePath && isInProjectPath(filePath, cwd)) {
106
+ project.push(loc);
107
+ } else {
108
+ external.push(loc);
109
+ }
110
+ }
111
+ return { project, external };
112
+ }
113
+
114
+ function formatSemanticImpls(
115
+ locations: Array<{
116
+ uri?: string;
117
+ targetUri?: string;
118
+ range?: { start: { line: number } };
119
+ targetRange?: { start: { line: number } };
120
+ }>,
121
+ cwd: string,
122
+ maxResults: number,
123
+ ): string {
124
+ const { project: projectLocs, external: externalLocs } = partitionImpls(locations, cwd);
125
+
126
+ const lines: string[] = [];
127
+ lines.push("# Implementations (semantic)");
128
+ lines.push("");
129
+ lines.push(`**${projectLocs.length} implementation${projectLocs.length !== 1 ? "s" : ""}**`);
130
+ if (externalLocs.length > 0) {
131
+ const suffix =
132
+ externalLocs.length === 1
133
+ ? "+1 external implementation"
134
+ : `+${externalLocs.length} external implementations`;
135
+ lines.push(`_${suffix} (node_modules, .pnpm, or out-of-tree)_`);
136
+ }
137
+ lines.push("");
138
+
139
+ let shown = 0;
140
+ for (const loc of projectLocs) {
141
+ if (shown >= maxResults) break;
142
+ const uri = loc.uri ?? loc.targetUri ?? "";
143
+ const range = loc.range ?? loc.targetRange ?? null;
144
+ const filePath = uriToFile(uri);
145
+ const implRelPath = path.relative(cwd, filePath);
146
+ const line = range ? range.start.line + 1 : 0;
147
+ lines.push(`- \`${implRelPath}\`:${line}`);
148
+ shown++;
149
+ }
150
+
151
+ if (projectLocs.length > maxResults) {
152
+ lines.push(`- _+${projectLocs.length - maxResults} more omitted_`);
153
+ }
154
+ lines.push("");
155
+ return lines.join("\n");
156
+ }
157
+
158
+ function formatHeuristicImpls(
159
+ symbol: string,
160
+ params: ActionParams,
161
+ cwd: string,
162
+ ): { content: string; matchCount: number } {
163
+ const scopePath = params.path ? normalizePath(params.path, cwd) : cwd;
164
+ const pattern = `(implements|extends)\\s+.*\\b${escapeRegex(symbol)}\\b`;
165
+ const matches = runRipgrep(pattern, scopePath, cwd, { maxMatches: 10 });
166
+
167
+ if (matches.length === 0) {
168
+ return {
169
+ content: `No implementations found for \`${symbol}\`.\n\nTry \`code_intel pattern\` with the type name.`,
170
+ matchCount: 0,
171
+ };
172
+ }
173
+
174
+ const lines: string[] = [];
175
+ lines.push(`# Implementations of \`${symbol}\` (heuristic)`);
176
+ lines.push("");
177
+ lines.push(
178
+ `**${matches.length} candidate${matches.length > 1 ? "s" : ""}** — text-search hints, not semantic implementations`,
179
+ );
180
+ lines.push("");
181
+
182
+ for (const m of matches.slice(0, 8)) {
183
+ lines.push(`- \`${m.file}\`:${m.line} — \`${m.text.slice(0, 80)}\``);
184
+ }
185
+ if (matches.length > 8) {
186
+ lines.push(`- _+${matches.length - 8} more omitted_`);
187
+ }
188
+ lines.push("");
189
+ return { content: lines.join("\n"), matchCount: matches.length };
190
+ }
@@ -0,0 +1,187 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { CodeIntelResult, SearchDetails } from "../types.ts";
4
+
5
+ const SOURCE_EXTENSIONS = new Map([
6
+ [".ts", "TypeScript"],
7
+ [".tsx", "TSX"],
8
+ [".js", "JavaScript"],
9
+ [".jsx", "JSX"],
10
+ [".mts", "TypeScript"],
11
+ [".cts", "TypeScript"],
12
+ [".mjs", "JavaScript"],
13
+ [".cjs", "JavaScript"],
14
+ [".py", "Python"],
15
+ [".pyi", "Python"],
16
+ [".rs", "Rust"],
17
+ [".go", "Go"],
18
+ [".mod", "Go"],
19
+ [".java", "Java"],
20
+ [".kt", "Kotlin"],
21
+ [".kts", "Kotlin"],
22
+ [".rb", "Ruby"],
23
+ [".php", "PHP"],
24
+ [".swift", "Swift"],
25
+ [".cpp", "C++"],
26
+ [".hpp", "C++"],
27
+ [".cc", "C++"],
28
+ [".cxx", "C++"],
29
+ [".hxx", "C++ Header"],
30
+ [".c++", "C++"],
31
+ [".h++", "C++ Header"],
32
+ [".c", "C"],
33
+ [".h", "C Header"],
34
+ [".css", "CSS"],
35
+ [".scss", "SCSS"],
36
+ [".less", "Less"],
37
+ [".html", "HTML"],
38
+ [".htm", "HTML"],
39
+ [".xhtml", "HTML"],
40
+ [".json", "JSON"],
41
+ [".yaml", "YAML"],
42
+ [".yml", "YAML"],
43
+ [".toml", "TOML"],
44
+ [".md", "Markdown"],
45
+ [".sh", "Shell"],
46
+ [".bash", "Shell"],
47
+ [".zsh", "Shell"],
48
+ [".r", "R"],
49
+ [".sql", "SQL"],
50
+ [".cs", "C#"],
51
+ ]);
52
+
53
+ const LANDMARK_FILES = new Set([
54
+ "package.json",
55
+ "tsconfig.json",
56
+ "jsconfig.json",
57
+ "vite.config.ts",
58
+ "vitest.config.ts",
59
+ "jest.config.ts",
60
+ "playwright.config.ts",
61
+ "biome.json",
62
+ "eslint.config.js",
63
+ ".eslintrc.js",
64
+ "deno.json",
65
+ "deno.jsonc",
66
+ "Cargo.toml",
67
+ "go.mod",
68
+ "pyproject.toml",
69
+ "setup.py",
70
+ "requirements.txt",
71
+ "Makefile",
72
+ "justfile",
73
+ "Taskfile.yml",
74
+ "Dockerfile",
75
+ "docker-compose.yml",
76
+ ".env.example",
77
+ ".env.local.example",
78
+ ]);
79
+
80
+ export function executeIndexAction(cwd: string): CodeIntelResult {
81
+ const stats = gatherStats(cwd);
82
+ const content = formatIndex(stats, cwd);
83
+ const details: SearchDetails = {
84
+ confidence: "structural",
85
+ scope: null,
86
+ candidateCount: stats.total,
87
+ omittedCount: 0,
88
+ nextQueries: ["`code_intel brief` for deeper context on a module"],
89
+ };
90
+ return { content, details: { type: "search" as const, data: details } };
91
+ }
92
+
93
+ interface FileStats {
94
+ byExtension: Map<string, number>;
95
+ byTopDir: Map<string, number>;
96
+ landmarkFiles: string[];
97
+ total: number;
98
+ }
99
+
100
+ const SKIP_DIRS = new Set(["node_modules", "dist", "build", ".git"]);
101
+
102
+ function shouldSkipEntry(entry: fs.Dirent): boolean {
103
+ return entry.name.startsWith(".") || SKIP_DIRS.has(entry.name);
104
+ }
105
+
106
+ function gatherStats(cwd: string): FileStats {
107
+ const byExtension = new Map<string, number>();
108
+ const byTopDir = new Map<string, number>();
109
+ const landmarkFiles: string[] = [];
110
+ let total = 0;
111
+
112
+ function processFile(entryRel: string, entryName: string) {
113
+ total++;
114
+ const ext = path.extname(entryName).toLowerCase();
115
+ byExtension.set(ext, (byExtension.get(ext) ?? 0) + 1);
116
+
117
+ const topDir = entryRel.split("/")[0] ?? ".";
118
+ byTopDir.set(topDir, (byTopDir.get(topDir) ?? 0) + 1);
119
+
120
+ if (LANDMARK_FILES.has(entryName)) {
121
+ landmarkFiles.push(entryRel);
122
+ }
123
+ }
124
+
125
+ function walk(dir: string, rel: string) {
126
+ let entries: fs.Dirent[];
127
+ try {
128
+ entries = fs.readdirSync(dir, { withFileTypes: true });
129
+ } catch {
130
+ return;
131
+ }
132
+
133
+ for (const entry of entries) {
134
+ if (shouldSkipEntry(entry)) continue;
135
+
136
+ const entryRel = rel ? `${rel}/${entry.name}` : entry.name;
137
+ const fullPath = path.join(dir, entry.name);
138
+
139
+ if (entry.isDirectory()) {
140
+ walk(fullPath, entryRel);
141
+ } else {
142
+ processFile(entryRel, entry.name);
143
+ }
144
+ }
145
+ }
146
+
147
+ walk(cwd, "");
148
+ return { byExtension, byTopDir, landmarkFiles, total };
149
+ }
150
+
151
+ function formatIndex(stats: FileStats, cwd: string): string {
152
+ const lines: string[] = [];
153
+ const name = path.basename(cwd);
154
+
155
+ lines.push(`# Project Map: ${name}`);
156
+ lines.push("");
157
+
158
+ lines.push(`**Source files:** ${stats.total} total`);
159
+ for (const [ext, count] of [...stats.byExtension.entries()]
160
+ .sort((a, b) => b[1] - a[1])
161
+ .slice(0, 10)) {
162
+ const label = SOURCE_EXTENSIONS.get(ext) ?? (ext || "(no extension)");
163
+ lines.push(`- ${label}: ${count}`);
164
+ }
165
+ if (stats.byExtension.size > 10) {
166
+ lines.push(`- _+${stats.byExtension.size - 10} more extensions_`);
167
+ }
168
+ lines.push("");
169
+
170
+ if (stats.byTopDir.size > 0) {
171
+ lines.push("**Top-level directories:**");
172
+ for (const [dir, count] of [...stats.byTopDir.entries()].sort((a, b) => b[1] - a[1])) {
173
+ lines.push(`- ${dir}/ (${count} file${count !== 1 ? "s" : ""})`);
174
+ }
175
+ lines.push("");
176
+ }
177
+
178
+ if (stats.landmarkFiles.length > 0) {
179
+ lines.push("**Landmark files:**");
180
+ for (const f of stats.landmarkFiles) {
181
+ lines.push(`- \`${f}\``);
182
+ }
183
+ lines.push("");
184
+ }
185
+
186
+ return lines.join("\n");
187
+ }