@sean.holung/minicode 0.3.6 → 0.3.8
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 +2 -1
- package/dist/scripts/run-benchmarks.js +1 -0
- package/dist/src/agent/config.js +27 -0
- package/dist/src/agent/editable-config.js +6 -0
- package/dist/src/model-utils.js +18 -1
- package/dist/src/serve/agent-bridge.js +85 -14
- package/dist/src/serve/mcp-server.js +19 -13
- package/dist/src/serve/server.js +166 -3
- package/dist/src/session/session-store.js +18 -0
- package/dist/src/shared/symbol-search.js +156 -0
- package/dist/src/tools/search-code-map.js +27 -35
- package/dist/src/web/app.js +662 -113
- package/dist/src/web/index.html +128 -8
- package/dist/src/web/style.css +189 -7
- package/dist/tests/agent.test.js +16 -0
- package/dist/tests/config-api.test.js +5 -0
- package/dist/tests/config-integration.test.js +91 -1
- package/dist/tests/config.test.js +9 -0
- package/dist/tests/file-tools.test.js +12 -0
- package/dist/tests/graph-onboarding.test.js +20 -0
- package/dist/tests/mcp-and-plugin.test.js +3 -0
- package/dist/tests/model-client-openai.test.js +41 -0
- package/dist/tests/model-dropdown-ui.test.js +23 -0
- package/dist/tests/model-utils.test.js +26 -1
- package/dist/tests/search-code-map.test.js +9 -0
- package/dist/tests/serve.integration.test.js +189 -0
- package/dist/tests/session-store.test.js +15 -1
- package/dist/tests/settings-ui.test.js +11 -0
- package/dist/tests/setup-overlay-state.test.js +49 -0
- package/dist/tests/system-prompt.test.js +1 -0
- package/dist/tests/test-utils.js +1 -0
- 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 +10 -1
- 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 +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts +8 -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 +164 -27
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.js +60 -6
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js +87 -0
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js +1 -0
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const MIN_SIMILARITY_QUERY_LENGTH = 3;
|
|
2
|
+
const MIN_SIMILARITY_SCORE = 0.62;
|
|
3
|
+
function normalizeSearchText(value) {
|
|
4
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
5
|
+
}
|
|
6
|
+
function tokenizeSearchText(value) {
|
|
7
|
+
return value
|
|
8
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.split(/[^a-z0-9]+/)
|
|
11
|
+
.filter(Boolean);
|
|
12
|
+
}
|
|
13
|
+
function levenshteinDistance(a, b) {
|
|
14
|
+
if (a === b) {
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
if (a.length === 0) {
|
|
18
|
+
return b.length;
|
|
19
|
+
}
|
|
20
|
+
if (b.length === 0) {
|
|
21
|
+
return a.length;
|
|
22
|
+
}
|
|
23
|
+
const previous = new Array(b.length + 1);
|
|
24
|
+
const current = new Array(b.length + 1);
|
|
25
|
+
for (let j = 0; j <= b.length; j += 1) {
|
|
26
|
+
previous[j] = j;
|
|
27
|
+
}
|
|
28
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
29
|
+
current[0] = i;
|
|
30
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
31
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
32
|
+
current[j] = Math.min(current[j - 1] + 1, previous[j] + 1, previous[j - 1] + cost);
|
|
33
|
+
}
|
|
34
|
+
for (let j = 0; j <= b.length; j += 1) {
|
|
35
|
+
previous[j] = current[j];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return previous[b.length];
|
|
39
|
+
}
|
|
40
|
+
function diceCoefficient(a, b) {
|
|
41
|
+
if (a === b) {
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
if (a.length < 2 || b.length < 2) {
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
const bigrams = new Map();
|
|
48
|
+
for (let i = 0; i < a.length - 1; i += 1) {
|
|
49
|
+
const bigram = a.slice(i, i + 2);
|
|
50
|
+
bigrams.set(bigram, (bigrams.get(bigram) ?? 0) + 1);
|
|
51
|
+
}
|
|
52
|
+
let matches = 0;
|
|
53
|
+
for (let i = 0; i < b.length - 1; i += 1) {
|
|
54
|
+
const bigram = b.slice(i, i + 2);
|
|
55
|
+
const count = bigrams.get(bigram) ?? 0;
|
|
56
|
+
if (count > 0) {
|
|
57
|
+
bigrams.set(bigram, count - 1);
|
|
58
|
+
matches += 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return (2 * matches) / ((a.length - 1) + (b.length - 1));
|
|
62
|
+
}
|
|
63
|
+
function computeSimilarityScore(pattern, candidate) {
|
|
64
|
+
const normalizedPattern = normalizeSearchText(pattern);
|
|
65
|
+
const normalizedCandidate = normalizeSearchText(candidate);
|
|
66
|
+
if (!normalizedPattern || !normalizedCandidate) {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
if (normalizedPattern === normalizedCandidate) {
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
const maxLength = Math.max(normalizedPattern.length, normalizedCandidate.length, 1);
|
|
73
|
+
const lengthPenalty = Math.abs(normalizedCandidate.length - normalizedPattern.length) / maxLength;
|
|
74
|
+
let bestScore = 0;
|
|
75
|
+
if (normalizedCandidate.startsWith(normalizedPattern) ||
|
|
76
|
+
normalizedPattern.startsWith(normalizedCandidate)) {
|
|
77
|
+
bestScore = Math.max(bestScore, 0.93 - (lengthPenalty * 0.2));
|
|
78
|
+
}
|
|
79
|
+
const tokens = tokenizeSearchText(candidate);
|
|
80
|
+
if (tokens.some((token) => token.startsWith(normalizedPattern) || normalizedPattern.startsWith(token))) {
|
|
81
|
+
bestScore = Math.max(bestScore, 0.88 - (lengthPenalty * 0.15));
|
|
82
|
+
}
|
|
83
|
+
const editDistance = levenshteinDistance(normalizedPattern, normalizedCandidate);
|
|
84
|
+
const maxDistance = Math.max(1, Math.ceil(normalizedPattern.length * 0.34));
|
|
85
|
+
if (editDistance <= maxDistance) {
|
|
86
|
+
const editSimilarity = 1 - (editDistance / maxLength);
|
|
87
|
+
bestScore = Math.max(bestScore, 0.62 + (editSimilarity * 0.3));
|
|
88
|
+
}
|
|
89
|
+
const dice = diceCoefficient(normalizedPattern, normalizedCandidate);
|
|
90
|
+
if (dice >= 0.5) {
|
|
91
|
+
bestScore = Math.max(bestScore, 0.45 + (dice * 0.35));
|
|
92
|
+
}
|
|
93
|
+
return bestScore;
|
|
94
|
+
}
|
|
95
|
+
function compareSubstringCandidates(pattern, a, b) {
|
|
96
|
+
const lowerPattern = pattern.toLowerCase();
|
|
97
|
+
const aExact = Number(a.lookupNames.some((value) => value.toLowerCase() === lowerPattern));
|
|
98
|
+
const bExact = Number(b.lookupNames.some((value) => value.toLowerCase() === lowerPattern));
|
|
99
|
+
if (aExact !== bExact) {
|
|
100
|
+
return bExact - aExact;
|
|
101
|
+
}
|
|
102
|
+
return Number(b.record.exported ?? false) - Number(a.record.exported ?? false) ||
|
|
103
|
+
a.record.name.localeCompare(b.record.name) ||
|
|
104
|
+
a.record.filePath.localeCompare(b.record.filePath) ||
|
|
105
|
+
a.record.startLine - b.record.startLine ||
|
|
106
|
+
a.record.qualifiedName.localeCompare(b.record.qualifiedName);
|
|
107
|
+
}
|
|
108
|
+
function compareSimilarCandidates(a, b) {
|
|
109
|
+
return b.score - a.score ||
|
|
110
|
+
Number(b.candidate.record.exported ?? false) - Number(a.candidate.record.exported ?? false) ||
|
|
111
|
+
a.candidate.record.name.localeCompare(b.candidate.record.name) ||
|
|
112
|
+
a.candidate.record.filePath.localeCompare(b.candidate.record.filePath) ||
|
|
113
|
+
a.candidate.record.startLine - b.candidate.record.startLine ||
|
|
114
|
+
a.candidate.record.qualifiedName.localeCompare(b.candidate.record.qualifiedName);
|
|
115
|
+
}
|
|
116
|
+
export function searchSymbols(candidates, pattern, options = {}) {
|
|
117
|
+
const lowerPattern = pattern.toLowerCase();
|
|
118
|
+
const normalizedKind = options.kind?.trim().toLowerCase();
|
|
119
|
+
const skip = Math.max(0, options.skip ?? 0);
|
|
120
|
+
const limit = Math.max(1, options.limit ?? 30);
|
|
121
|
+
const filtered = candidates.filter((candidate) => {
|
|
122
|
+
if (normalizedKind && candidate.record.kind.toLowerCase() !== normalizedKind) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
});
|
|
127
|
+
const substringMatches = filtered
|
|
128
|
+
.filter((candidate) => candidate.lookupNames.some((value) => value.toLowerCase().includes(lowerPattern)))
|
|
129
|
+
.sort((a, b) => compareSubstringCandidates(pattern, a, b));
|
|
130
|
+
if (substringMatches.length > 0) {
|
|
131
|
+
return {
|
|
132
|
+
matches: substringMatches.slice(skip, skip + limit).map((candidate) => candidate.symbol),
|
|
133
|
+
mode: "substring",
|
|
134
|
+
total: substringMatches.length,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const normalizedPattern = normalizeSearchText(pattern);
|
|
138
|
+
if (normalizedPattern.length < MIN_SIMILARITY_QUERY_LENGTH) {
|
|
139
|
+
return { matches: [], mode: "none", total: 0 };
|
|
140
|
+
}
|
|
141
|
+
const similarMatches = filtered
|
|
142
|
+
.map((candidate) => ({
|
|
143
|
+
candidate,
|
|
144
|
+
score: Math.max(...candidate.lookupNames.map((value) => computeSimilarityScore(pattern, value))),
|
|
145
|
+
}))
|
|
146
|
+
.filter((entry) => entry.score >= MIN_SIMILARITY_SCORE)
|
|
147
|
+
.sort(compareSimilarCandidates);
|
|
148
|
+
if (similarMatches.length === 0) {
|
|
149
|
+
return { matches: [], mode: "none", total: 0 };
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
matches: similarMatches.slice(skip, skip + limit).map((entry) => entry.candidate.symbol),
|
|
153
|
+
mode: "similar",
|
|
154
|
+
total: similarMatches.length,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -1,26 +1,7 @@
|
|
|
1
1
|
import { expectNonEmptyString, expectOptionalNumber } from "@minicode/agent-sdk";
|
|
2
2
|
import { getSymbolDisplayName, getSymbolLookupNames } from "../indexer/symbol-names.js";
|
|
3
|
+
import { searchSymbols } from "../shared/symbol-search.js";
|
|
3
4
|
const DEFAULT_LIMIT = 30;
|
|
4
|
-
function compareMatchedSymbols(pattern, a, b) {
|
|
5
|
-
const lowerPattern = pattern.toLowerCase();
|
|
6
|
-
const aDisplay = getSymbolDisplayName(a).toLowerCase();
|
|
7
|
-
const bDisplay = getSymbolDisplayName(b).toLowerCase();
|
|
8
|
-
const aExact = Number(aDisplay === lowerPattern || a.qualifiedName.toLowerCase() === lowerPattern || a.name.toLowerCase() === lowerPattern);
|
|
9
|
-
const bExact = Number(bDisplay === lowerPattern || b.qualifiedName.toLowerCase() === lowerPattern || b.name.toLowerCase() === lowerPattern);
|
|
10
|
-
if (aExact !== bExact) {
|
|
11
|
-
return bExact - aExact;
|
|
12
|
-
}
|
|
13
|
-
return Number(b.exported) - Number(a.exported) ||
|
|
14
|
-
aDisplay.localeCompare(bDisplay) ||
|
|
15
|
-
a.filePath.localeCompare(b.filePath) ||
|
|
16
|
-
a.startLine - b.startLine ||
|
|
17
|
-
a.qualifiedName.localeCompare(b.qualifiedName);
|
|
18
|
-
}
|
|
19
|
-
function matchesPattern(text, pattern) {
|
|
20
|
-
const lowerText = text.toLowerCase();
|
|
21
|
-
const lowerPattern = pattern.toLowerCase();
|
|
22
|
-
return lowerText.includes(lowerPattern);
|
|
23
|
-
}
|
|
24
5
|
export function createSearchCodeMapTool(projectIndex) {
|
|
25
6
|
return {
|
|
26
7
|
name: "search_code_map",
|
|
@@ -58,28 +39,39 @@ export function createSearchCodeMapTool(projectIndex) {
|
|
|
58
39
|
: undefined;
|
|
59
40
|
const limit = Math.max(1, Math.min(100, expectOptionalNumber(input, "limit") ?? DEFAULT_LIMIT));
|
|
60
41
|
const skip = Math.max(0, expectOptionalNumber(input, "skip") ?? 0);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
42
|
+
const result = searchSymbols([...projectIndex.symbols.values()].map((sym) => ({
|
|
43
|
+
symbol: sym,
|
|
44
|
+
record: {
|
|
45
|
+
name: getSymbolDisplayName(sym),
|
|
46
|
+
qualifiedName: sym.qualifiedName,
|
|
47
|
+
kind: sym.kind,
|
|
48
|
+
filePath: sym.filePath,
|
|
49
|
+
startLine: sym.startLine,
|
|
50
|
+
exported: sym.exported,
|
|
51
|
+
},
|
|
52
|
+
lookupNames: getSymbolLookupNames(sym),
|
|
53
|
+
})), pattern, { kind, limit, skip });
|
|
54
|
+
const shown = result.matches;
|
|
73
55
|
const lines = shown.map((s) => `- ${getSymbolDisplayName(s)} (${s.kind}) — ${s.filePath}:${s.startLine} — qualified: ${s.qualifiedName}`);
|
|
74
|
-
const remaining =
|
|
56
|
+
const remaining = result.total - skip - shown.length;
|
|
75
57
|
const footer = remaining > 0
|
|
76
58
|
? `\n... and ${remaining} more (use skip: ${skip + limit}, limit: ${limit} for next page)`
|
|
77
59
|
: "";
|
|
78
|
-
if (
|
|
60
|
+
if (result.total === 0) {
|
|
79
61
|
return `No symbols matching "${pattern}"${kind ? ` (kind: ${kind})` : ""}. Try a shorter or different pattern.`;
|
|
80
62
|
}
|
|
63
|
+
if (result.mode === "similar") {
|
|
64
|
+
return [
|
|
65
|
+
`# No exact substring matches for "${pattern}"${kind ? ` (kind: ${kind})` : ""}`,
|
|
66
|
+
"",
|
|
67
|
+
`Showing similar symbols instead (${result.total} total):`,
|
|
68
|
+
"",
|
|
69
|
+
...lines,
|
|
70
|
+
footer,
|
|
71
|
+
].join("\n");
|
|
72
|
+
}
|
|
81
73
|
return [
|
|
82
|
-
`# Symbols matching "${pattern}" (${
|
|
74
|
+
`# Symbols matching "${pattern}" (${result.total} total)`,
|
|
83
75
|
"",
|
|
84
76
|
...lines,
|
|
85
77
|
footer,
|