@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.
- package/README.md +212 -0
- package/node_modules/@mrclrchtr/supi-core/README.md +90 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +30 -0
- package/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
- package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +112 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +90 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +30 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/capabilities.ts +62 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-refresh.ts +229 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +545 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +192 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config.ts +143 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/defaults.json +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-augmentation.ts +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-display.ts +68 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +73 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostics.ts +98 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +47 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/suppression-diagnostics.ts +58 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +359 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/guidance.ts +163 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/index.ts +17 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp-state.ts +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +470 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +34 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +139 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +39 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +46 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-types.ts +39 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +83 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +18 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +550 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/overrides.ts +173 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +197 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/renderer.ts +120 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/scanner.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/search-fallback.ts +98 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/service-registry.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/settings-registration.ts +292 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tool-actions.ts +430 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tree-persist.ts +48 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tsconfig-scope.ts +156 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/types.ts +409 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/ui.ts +358 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +122 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/workspace-sentinels.ts +114 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +97 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +67 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/.gitkeep +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +12 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +19 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +126 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +144 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +151 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/callees.ts +343 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +108 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/exports.ts +315 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/formatting.ts +104 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/imports.ts +42 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +16 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +116 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/node-at.ts +96 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/outline.ts +287 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/runtime.ts +237 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session.ts +112 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/structure.ts +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +13 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +306 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +146 -0
- package/package.json +47 -0
- package/src/actions/affected-action.ts +310 -0
- package/src/actions/brief-action.ts +242 -0
- package/src/actions/callees-action.ts +134 -0
- package/src/actions/callers-action.ts +215 -0
- package/src/actions/implementations-action.ts +190 -0
- package/src/actions/index-action.ts +187 -0
- package/src/actions/pattern-action.ts +232 -0
- package/src/architecture.ts +367 -0
- package/src/brief-focused.ts +383 -0
- package/src/brief.ts +228 -0
- package/src/code-intelligence.ts +122 -0
- package/src/git-context.ts +65 -0
- package/src/guidance.ts +39 -0
- package/src/index.ts +28 -0
- package/src/resolve-target.ts +104 -0
- package/src/search-helpers.ts +283 -0
- package/src/target-resolution.ts +368 -0
- package/src/tool-actions.ts +109 -0
- 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
|
+
}
|