@sean.holung/minicode 0.3.2 → 0.3.3
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 +48 -43
- package/dist/scripts/run-benchmarks.js +147 -0
- package/dist/src/agent/config.js +149 -40
- package/dist/src/agent/editable-config.js +314 -0
- package/dist/src/analysis/structural-analysis.js +379 -0
- package/dist/src/benchmark/evaluator.js +79 -0
- package/dist/src/benchmark/index.js +4 -0
- package/dist/src/benchmark/reporter.js +177 -0
- package/dist/src/benchmark/runner.js +100 -0
- package/dist/src/benchmark/task-loader.js +78 -0
- package/dist/src/benchmark/types.js +5 -0
- package/dist/src/cli/args.js +10 -0
- package/dist/src/cli/config-slash-command.js +135 -0
- package/dist/src/cli/plugin-install.js +69 -0
- package/dist/src/index.js +76 -6
- package/dist/src/indexer/cache.js +6 -4
- package/dist/src/indexer/code-map.js +41 -13
- package/dist/src/indexer/plugins/typescript.js +70 -23
- package/dist/src/indexer/project-index.js +175 -36
- package/dist/src/indexer/symbol-names.js +92 -0
- package/dist/src/model-utils.js +18 -0
- package/dist/src/serve/agent-bridge.js +203 -24
- package/dist/src/serve/mcp-server.js +405 -0
- package/dist/src/serve/server.js +165 -10
- package/dist/src/serve/websocket.js +8 -0
- package/dist/src/shared/graph-styles.js +119 -0
- package/dist/src/tools/find-path.js +75 -0
- package/dist/src/tools/find-references.js +7 -2
- package/dist/src/tools/get-dependencies.js +3 -2
- package/dist/src/tools/read-symbol.js +12 -5
- package/dist/src/tools/registry.js +3 -1
- package/dist/src/tools/search-code-map.js +4 -2
- package/dist/src/ui/app.js +1 -1
- package/dist/src/ui/cli-ink.js +79 -4
- package/dist/src/ui/components/header-bar.js +6 -2
- package/dist/src/ui/state/ui-store.js +5 -0
- package/dist/src/web/app.js +1124 -176
- package/dist/src/web/index.html +113 -3
- package/dist/src/web/style.css +973 -55
- package/dist/tests/agent.test.js +31 -0
- package/dist/tests/analysis-helpers.test.js +89 -0
- package/dist/tests/analysis-ui.test.js +29 -0
- package/dist/tests/benchmark-harness.test.js +527 -0
- package/dist/tests/config-api.test.js +143 -0
- package/dist/tests/config-integration.test.js +751 -0
- package/dist/tests/config-slash-command.test.js +106 -0
- package/dist/tests/config.test.js +42 -1
- package/dist/tests/context-indicator.test.js +220 -0
- package/dist/tests/editable-config.test.js +109 -0
- package/dist/tests/find-path.test.js +183 -0
- package/dist/tests/focus-tracker.test.js +62 -0
- package/dist/tests/graph-onboarding.test.js +55 -0
- package/dist/tests/graph-styles.test.js +65 -0
- package/dist/tests/indexer.test.js +137 -0
- package/dist/tests/mcp-and-plugin.test.js +186 -0
- package/dist/tests/model-client-openai.test.js +29 -0
- package/dist/tests/model-selection.test.js +136 -0
- package/dist/tests/model-utils.test.js +22 -0
- package/dist/tests/reasoning-effort.test.js +264 -0
- package/dist/tests/run-benchmarks.test.js +161 -0
- package/dist/tests/search-code-map.test.js +18 -0
- package/dist/tests/serve.integration.test.js +218 -2
- package/dist/tests/session-ui.test.js +21 -0
- package/dist/tests/session.test.js +50 -0
- package/dist/tests/settings-ui.test.js +30 -0
- package/dist/tests/structural-analysis.test.js +218 -0
- package/node_modules/@minicode/agent-sdk/README.md +80 -51
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts +16 -5
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js +51 -33
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts +14 -0
- package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts +3 -2
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.js +2 -0
- package/node_modules/@minicode/agent-sdk/dist/src/index.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/focus-tracker.d.ts +35 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/focus-tracker.d.ts.map +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/focus-tracker.js +64 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/focus-tracker.js.map +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.d.ts +7 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts +5 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js +83 -11
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/safety/guardrails.d.ts +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/safety/guardrails.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/safety/guardrails.js +8 -1
- package/node_modules/@minicode/agent-sdk/dist/src/safety/guardrails.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.js +4 -1
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js +3 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/agent.test.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js +8 -2
- package/node_modules/@minicode/agent-sdk/dist/tests/guardrails.test.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -5
- package/plugin/.claude-plugin/plugin.json +12 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/CLAUDE.md +26 -0
- package/plugin/skills/analyze/SKILL.md +12 -0
- package/plugin/skills/focus/SKILL.md +20 -0
- package/plugin/skills/graph/SKILL.md +13 -0
- package/plugin/skills/symbols/SKILL.md +13 -0
|
@@ -2,26 +2,75 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { generateCodeMap } from "./code-map.js";
|
|
4
4
|
import { getPluginForFile, loadPlugins } from "./plugin-loader.js";
|
|
5
|
+
import { getSymbolLookupNames, normalizeIndexedSymbols } from "./symbol-names.js";
|
|
5
6
|
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "coverage"]);
|
|
6
|
-
async function collectSourceFiles(dir, root, files) {
|
|
7
|
+
async function collectSourceFiles(dir, root, files, validExtensions) {
|
|
7
8
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
8
9
|
for (const entry of entries) {
|
|
9
10
|
const fullPath = path.join(dir, entry.name);
|
|
10
11
|
if (entry.isDirectory()) {
|
|
11
12
|
if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
12
|
-
await collectSourceFiles(fullPath, root, files);
|
|
13
|
+
await collectSourceFiles(fullPath, root, files, validExtensions);
|
|
13
14
|
}
|
|
14
15
|
continue;
|
|
15
16
|
}
|
|
16
17
|
if (entry.isFile()) {
|
|
17
18
|
const ext = path.extname(entry.name).toLowerCase();
|
|
18
|
-
if (
|
|
19
|
+
if (validExtensions.has(ext)) {
|
|
19
20
|
files.push(path.relative(root, fullPath));
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
function buildAdjacencyFrom(edges) {
|
|
26
|
+
const map = new Map();
|
|
27
|
+
for (const edge of edges) {
|
|
28
|
+
const list = map.get(edge.from);
|
|
29
|
+
if (list) {
|
|
30
|
+
list.push(edge);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
map.set(edge.from, [edge]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return map;
|
|
37
|
+
}
|
|
38
|
+
function buildAdjacencyTo(edges) {
|
|
39
|
+
const map = new Map();
|
|
40
|
+
for (const edge of edges) {
|
|
41
|
+
const list = map.get(edge.to);
|
|
42
|
+
if (list) {
|
|
43
|
+
list.push(edge);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
map.set(edge.to, [edge]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return map;
|
|
50
|
+
}
|
|
51
|
+
function resolveSymbol(name, symbols) {
|
|
52
|
+
const direct = symbols.get(name);
|
|
53
|
+
if (direct)
|
|
54
|
+
return direct;
|
|
55
|
+
const matches = [...symbols.values()].filter((sym) => getSymbolLookupNames(sym).includes(name));
|
|
56
|
+
if (matches.length === 0) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
matches.sort((a, b) => Number(b.exported) - Number(a.exported) ||
|
|
60
|
+
a.filePath.localeCompare(b.filePath) ||
|
|
61
|
+
a.startLine - b.startLine ||
|
|
62
|
+
a.qualifiedName.localeCompare(b.qualifiedName));
|
|
63
|
+
return matches[0];
|
|
64
|
+
}
|
|
24
65
|
export function createProjectIndex(symbols, files, dependencyEdges, plugins, projectFiles, workspaceRoot) {
|
|
66
|
+
let adjacencyFrom = buildAdjacencyFrom(dependencyEdges);
|
|
67
|
+
function rebuildSymbolsMap() {
|
|
68
|
+
const normalizedSymbols = normalizeIndexedSymbols(files);
|
|
69
|
+
symbols.clear();
|
|
70
|
+
for (const [qualifiedName, symbol] of normalizedSymbols) {
|
|
71
|
+
symbols.set(qualifiedName, symbol);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
25
74
|
return {
|
|
26
75
|
symbols,
|
|
27
76
|
files,
|
|
@@ -30,20 +79,13 @@ export function createProjectIndex(symbols, files, dependencyEdges, plugins, pro
|
|
|
30
79
|
projectFiles,
|
|
31
80
|
workspaceRoot,
|
|
32
81
|
getSymbol(name) {
|
|
33
|
-
|
|
34
|
-
if (direct)
|
|
35
|
-
return direct;
|
|
36
|
-
for (const sym of symbols.values()) {
|
|
37
|
-
if (sym.name === name || sym.qualifiedName === name)
|
|
38
|
-
return sym;
|
|
39
|
-
}
|
|
40
|
-
return undefined;
|
|
82
|
+
return resolveSymbol(name, symbols);
|
|
41
83
|
},
|
|
42
84
|
getSymbolsInFile(filePath) {
|
|
43
85
|
return files.get(filePath) ?? [];
|
|
44
86
|
},
|
|
45
87
|
getDependencyCone(symbolName, depth = 2) {
|
|
46
|
-
const target =
|
|
88
|
+
const target = resolveSymbol(symbolName, symbols);
|
|
47
89
|
if (!target)
|
|
48
90
|
return [];
|
|
49
91
|
const result = new Map();
|
|
@@ -52,13 +94,14 @@ export function createProjectIndex(symbols, files, dependencyEdges, plugins, pro
|
|
|
52
94
|
for (let d = 0; d < depth; d += 1) {
|
|
53
95
|
const next = new Set();
|
|
54
96
|
for (const from of frontier) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
97
|
+
const outEdges = adjacencyFrom.get(from);
|
|
98
|
+
if (!outEdges)
|
|
99
|
+
continue;
|
|
100
|
+
for (const edge of outEdges) {
|
|
101
|
+
const dep = resolveSymbol(edge.to, symbols);
|
|
102
|
+
if (dep && !result.has(dep.qualifiedName)) {
|
|
103
|
+
result.set(dep.qualifiedName, dep);
|
|
104
|
+
next.add(dep.qualifiedName);
|
|
62
105
|
}
|
|
63
106
|
}
|
|
64
107
|
}
|
|
@@ -66,6 +109,108 @@ export function createProjectIndex(symbols, files, dependencyEdges, plugins, pro
|
|
|
66
109
|
}
|
|
67
110
|
return [...result.values()];
|
|
68
111
|
},
|
|
112
|
+
findPath(fromSymbol, toSymbol, maxDepth = 10) {
|
|
113
|
+
const from = resolveSymbol(fromSymbol, symbols);
|
|
114
|
+
const to = resolveSymbol(toSymbol, symbols);
|
|
115
|
+
if (!from || !to)
|
|
116
|
+
return [];
|
|
117
|
+
// BFS over both outgoing and incoming edges (undirected search)
|
|
118
|
+
const adjacencyTo = buildAdjacencyTo(dependencyEdges);
|
|
119
|
+
const visited = new Set();
|
|
120
|
+
const parent = new Map();
|
|
121
|
+
const queue = [
|
|
122
|
+
{ name: from.qualifiedName, depth: 0 },
|
|
123
|
+
];
|
|
124
|
+
visited.add(from.qualifiedName);
|
|
125
|
+
while (queue.length > 0) {
|
|
126
|
+
const current = queue.shift();
|
|
127
|
+
if (current.name === to.qualifiedName) {
|
|
128
|
+
// Reconstruct path
|
|
129
|
+
const path = [];
|
|
130
|
+
let node = to.qualifiedName;
|
|
131
|
+
while (node !== undefined) {
|
|
132
|
+
const sym = resolveSymbol(node, symbols);
|
|
133
|
+
if (sym)
|
|
134
|
+
path.unshift(sym);
|
|
135
|
+
node = parent.get(node);
|
|
136
|
+
}
|
|
137
|
+
return path;
|
|
138
|
+
}
|
|
139
|
+
if (current.depth >= maxDepth)
|
|
140
|
+
continue;
|
|
141
|
+
// Follow outgoing edges
|
|
142
|
+
const outEdges = adjacencyFrom.get(current.name) ?? [];
|
|
143
|
+
for (const edge of outEdges) {
|
|
144
|
+
const target = resolveSymbol(edge.to, symbols);
|
|
145
|
+
if (target && !visited.has(target.qualifiedName)) {
|
|
146
|
+
visited.add(target.qualifiedName);
|
|
147
|
+
parent.set(target.qualifiedName, current.name);
|
|
148
|
+
queue.push({ name: target.qualifiedName, depth: current.depth + 1 });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Follow incoming edges (reverse direction)
|
|
152
|
+
const inEdges = adjacencyTo.get(current.name) ?? [];
|
|
153
|
+
for (const edge of inEdges) {
|
|
154
|
+
const source = resolveSymbol(edge.from, symbols);
|
|
155
|
+
if (source && !visited.has(source.qualifiedName)) {
|
|
156
|
+
visited.add(source.qualifiedName);
|
|
157
|
+
parent.set(source.qualifiedName, current.name);
|
|
158
|
+
queue.push({ name: source.qualifiedName, depth: current.depth + 1 });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return [];
|
|
163
|
+
},
|
|
164
|
+
findPathToEntryPoint(symbolName, maxDepth = 20) {
|
|
165
|
+
const target = resolveSymbol(symbolName, symbols);
|
|
166
|
+
if (!target)
|
|
167
|
+
return [];
|
|
168
|
+
// Build reverse adjacency: who calls/references this symbol?
|
|
169
|
+
const adjacencyTo = buildAdjacencyTo(dependencyEdges);
|
|
170
|
+
// Find entry points: symbols that have no inbound edges
|
|
171
|
+
const hasInbound = new Set();
|
|
172
|
+
for (const edge of dependencyEdges) {
|
|
173
|
+
hasInbound.add(edge.to);
|
|
174
|
+
}
|
|
175
|
+
// If the target itself is already an entry point (no inbound edges), nothing to trace
|
|
176
|
+
if (!hasInbound.has(target.qualifiedName))
|
|
177
|
+
return [];
|
|
178
|
+
// DFS from target following inbound edges back to entry points
|
|
179
|
+
const paths = [];
|
|
180
|
+
const currentPath = [target];
|
|
181
|
+
const visiting = new Set([target.qualifiedName]);
|
|
182
|
+
function dfs(node, depth) {
|
|
183
|
+
if (depth >= maxDepth)
|
|
184
|
+
return;
|
|
185
|
+
const inEdges = adjacencyTo.get(node) ?? [];
|
|
186
|
+
const callers = inEdges
|
|
187
|
+
.map((e) => resolveSymbol(e.from, symbols))
|
|
188
|
+
.filter((s) => s != null && !visiting.has(s.qualifiedName));
|
|
189
|
+
if (callers.length === 0) {
|
|
190
|
+
// Dead end going backwards — this is as far as we can trace
|
|
191
|
+
if (currentPath.length > 1) {
|
|
192
|
+
paths.push([...currentPath].reverse());
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
for (const caller of callers) {
|
|
197
|
+
// If this caller is an entry point (no inbound edges), record the path
|
|
198
|
+
if (!hasInbound.has(caller.qualifiedName)) {
|
|
199
|
+
currentPath.push(caller);
|
|
200
|
+
paths.push([...currentPath].reverse());
|
|
201
|
+
currentPath.pop();
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
visiting.add(caller.qualifiedName);
|
|
205
|
+
currentPath.push(caller);
|
|
206
|
+
dfs(caller.qualifiedName, depth + 1);
|
|
207
|
+
currentPath.pop();
|
|
208
|
+
visiting.delete(caller.qualifiedName);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
dfs(target.qualifiedName, 0);
|
|
212
|
+
return paths.slice(0, 10);
|
|
213
|
+
},
|
|
69
214
|
getCodeMap(tokenBudget, focusSymbols) {
|
|
70
215
|
return generateCodeMap(files, tokenBudget, dependencyEdges, focusSymbols);
|
|
71
216
|
},
|
|
@@ -76,24 +221,17 @@ export function createProjectIndex(symbols, files, dependencyEdges, plugins, pro
|
|
|
76
221
|
const plugin = getPluginForFile(relPath, plugins);
|
|
77
222
|
if (!plugin)
|
|
78
223
|
return;
|
|
79
|
-
const oldSymbols = files.get(relPath) ?? [];
|
|
80
|
-
for (const sym of oldSymbols) {
|
|
81
|
-
symbols.delete(sym.qualifiedName);
|
|
82
|
-
}
|
|
83
224
|
files.delete(relPath);
|
|
84
225
|
projectFiles.set(relPath, content);
|
|
85
226
|
const extracted = plugin.indexFile(relPath, content);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const existing = files.get(relPath) ?? [];
|
|
89
|
-
existing.push(sym);
|
|
90
|
-
files.set(relPath, existing);
|
|
91
|
-
}
|
|
227
|
+
files.set(relPath, extracted);
|
|
228
|
+
rebuildSymbolsMap();
|
|
92
229
|
for (const p of plugins) {
|
|
93
230
|
if (p.resolveDependencies) {
|
|
94
231
|
const allSymbols = [...symbols.values()];
|
|
95
232
|
const edges = p.resolveDependencies(allSymbols, projectFiles);
|
|
96
233
|
dependencyEdges.splice(0, dependencyEdges.length, ...edges);
|
|
234
|
+
adjacencyFrom = buildAdjacencyFrom(dependencyEdges);
|
|
97
235
|
break;
|
|
98
236
|
}
|
|
99
237
|
}
|
|
@@ -106,8 +244,9 @@ export function createProjectIndex(symbols, files, dependencyEdges, plugins, pro
|
|
|
106
244
|
export async function buildProjectIndex(workspaceRoot) {
|
|
107
245
|
const plugins = await loadPlugins(workspaceRoot);
|
|
108
246
|
const root = path.resolve(workspaceRoot);
|
|
247
|
+
const validExtensions = new Set(plugins.flatMap((p) => p.extensions));
|
|
109
248
|
const sourceFiles = [];
|
|
110
|
-
await collectSourceFiles(root, root, sourceFiles);
|
|
249
|
+
await collectSourceFiles(root, root, sourceFiles, validExtensions);
|
|
111
250
|
const symbols = new Map();
|
|
112
251
|
const files = new Map();
|
|
113
252
|
const projectFiles = new Map();
|
|
@@ -125,12 +264,12 @@ export async function buildProjectIndex(workspaceRoot) {
|
|
|
125
264
|
}
|
|
126
265
|
projectFiles.set(relPath, content);
|
|
127
266
|
const extracted = plugin.indexFile(relPath, content);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
267
|
+
files.set(relPath, extracted);
|
|
268
|
+
}
|
|
269
|
+
const normalizedSymbols = normalizeIndexedSymbols(files);
|
|
270
|
+
symbols.clear();
|
|
271
|
+
for (const [qualifiedName, symbol] of normalizedSymbols) {
|
|
272
|
+
symbols.set(qualifiedName, symbol);
|
|
134
273
|
}
|
|
135
274
|
let dependencyEdges = [];
|
|
136
275
|
for (const plugin of plugins) {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
function dedupe(values) {
|
|
2
|
+
return [...new Set(values.filter((value) => value.length > 0))];
|
|
3
|
+
}
|
|
4
|
+
function buildDisplayName(baseName, symbol, kindCount, distinctFileCount) {
|
|
5
|
+
if (distinctFileCount > 1) {
|
|
6
|
+
return `${baseName} (${symbol.kind} in ${symbol.filePath}:${symbol.startLine})`;
|
|
7
|
+
}
|
|
8
|
+
if (kindCount === 1) {
|
|
9
|
+
return `${baseName} (${symbol.kind})`;
|
|
10
|
+
}
|
|
11
|
+
return `${baseName} (${symbol.kind}:${symbol.startLine})`;
|
|
12
|
+
}
|
|
13
|
+
function buildQualifiedName(baseName, symbol, kindCount, distinctFileCount) {
|
|
14
|
+
if (distinctFileCount > 1) {
|
|
15
|
+
return `${baseName}#${symbol.kind}@${symbol.filePath}:${symbol.startLine}`;
|
|
16
|
+
}
|
|
17
|
+
if (kindCount === 1) {
|
|
18
|
+
return `${baseName}#${symbol.kind}`;
|
|
19
|
+
}
|
|
20
|
+
return `${baseName}#${symbol.kind}:${symbol.startLine}`;
|
|
21
|
+
}
|
|
22
|
+
function countByKind(symbols) {
|
|
23
|
+
const counts = new Map();
|
|
24
|
+
for (const symbol of symbols) {
|
|
25
|
+
counts.set(symbol.kind, (counts.get(symbol.kind) ?? 0) + 1);
|
|
26
|
+
}
|
|
27
|
+
return counts;
|
|
28
|
+
}
|
|
29
|
+
function countByFile(symbols) {
|
|
30
|
+
const counts = new Map();
|
|
31
|
+
for (const symbol of symbols) {
|
|
32
|
+
counts.set(symbol.filePath, (counts.get(symbol.filePath) ?? 0) + 1);
|
|
33
|
+
}
|
|
34
|
+
return counts;
|
|
35
|
+
}
|
|
36
|
+
export function getSymbolDisplayName(symbol) {
|
|
37
|
+
return symbol.displayName ?? symbol.qualifiedName;
|
|
38
|
+
}
|
|
39
|
+
export function getSymbolLookupNames(symbol) {
|
|
40
|
+
return dedupe([
|
|
41
|
+
symbol.qualifiedName,
|
|
42
|
+
symbol.originalQualifiedName ?? "",
|
|
43
|
+
symbol.displayName ?? "",
|
|
44
|
+
symbol.name,
|
|
45
|
+
...(symbol.aliases ?? []),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
export function normalizeIndexedSymbols(symbolsByFile) {
|
|
49
|
+
const allSymbols = [...symbolsByFile.values()].flat();
|
|
50
|
+
const groupedByBaseName = new Map();
|
|
51
|
+
for (const symbol of allSymbols) {
|
|
52
|
+
const baseName = symbol.originalQualifiedName ?? symbol.qualifiedName;
|
|
53
|
+
symbol.originalQualifiedName = baseName;
|
|
54
|
+
symbol.displayName = symbol.displayName ?? baseName;
|
|
55
|
+
symbol.aliases = dedupe([
|
|
56
|
+
symbol.name,
|
|
57
|
+
baseName,
|
|
58
|
+
symbol.displayName,
|
|
59
|
+
...(symbol.aliases ?? []),
|
|
60
|
+
]);
|
|
61
|
+
const existing = groupedByBaseName.get(baseName);
|
|
62
|
+
if (existing) {
|
|
63
|
+
existing.push(symbol);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
groupedByBaseName.set(baseName, [symbol]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const [baseName, group] of groupedByBaseName) {
|
|
70
|
+
if (group.length < 2)
|
|
71
|
+
continue;
|
|
72
|
+
const sorted = [...group].sort((a, b) => a.filePath.localeCompare(b.filePath) ||
|
|
73
|
+
a.startLine - b.startLine ||
|
|
74
|
+
a.kind.localeCompare(b.kind));
|
|
75
|
+
const kindCounts = countByKind(sorted);
|
|
76
|
+
const distinctFileCount = countByFile(sorted).size;
|
|
77
|
+
for (const symbol of sorted) {
|
|
78
|
+
const kindCount = kindCounts.get(symbol.kind) ?? 1;
|
|
79
|
+
const displayName = buildDisplayName(baseName, symbol, kindCount, distinctFileCount);
|
|
80
|
+
const qualifiedName = buildQualifiedName(baseName, symbol, kindCount, distinctFileCount);
|
|
81
|
+
symbol.displayName = displayName;
|
|
82
|
+
symbol.aliases = dedupe([
|
|
83
|
+
symbol.name,
|
|
84
|
+
baseName,
|
|
85
|
+
displayName,
|
|
86
|
+
...(symbol.aliases ?? []),
|
|
87
|
+
]);
|
|
88
|
+
symbol.qualifiedName = qualifiedName;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return new Map(allSymbols.map((symbol) => [symbol.qualifiedName, symbol]));
|
|
92
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function getModelDisplayName(model) {
|
|
2
|
+
return (model.name ?? model.id).trim();
|
|
3
|
+
}
|
|
4
|
+
export function sortModelsAlphabetically(models) {
|
|
5
|
+
return [...models].sort((left, right) => {
|
|
6
|
+
const byDisplayName = getModelDisplayName(left).localeCompare(getModelDisplayName(right), undefined, {
|
|
7
|
+
sensitivity: "base",
|
|
8
|
+
numeric: true,
|
|
9
|
+
});
|
|
10
|
+
if (byDisplayName !== 0) {
|
|
11
|
+
return byDisplayName;
|
|
12
|
+
}
|
|
13
|
+
return left.id.localeCompare(right.id, undefined, {
|
|
14
|
+
sensitivity: "base",
|
|
15
|
+
numeric: true,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|