@takara-ai/miru-code 1.1.2 → 1.2.1
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 +178 -107
- package/package.json +9 -5
- package/src/benchmark/compare.ts +278 -0
- package/src/benchmark/grep.ts +352 -0
- package/src/benchmark/history.ts +206 -0
- package/src/benchmark/locate-compare.ts +65 -0
- package/src/benchmark/rg-literal.ts +143 -0
- package/src/benchmark/summary.ts +29 -0
- package/src/benchmark/types.ts +65 -0
- package/src/cli.ts +185 -4
- package/src/credentials.ts +8 -0
- package/src/help.ts +45 -2
- package/src/index/create.ts +2 -2
- package/src/index/incremental.ts +4 -3
- package/src/index.ts +3 -1
- package/src/installer/benchmark-mode.ts +248 -0
- package/src/installer/config.ts +14 -2
- package/src/installer/hooks/opencode-plugin.ts +6 -20
- package/src/installer/hooks/search-guard.ts +4 -5
- package/src/installer/installer.ts +59 -6
- package/src/installer/search-policy.ts +28 -9
- package/src/literal.ts +139 -0
- package/src/mcp/index-cache.ts +2 -2
- package/src/mcp/serve.ts +4 -3
- package/src/mcp/server.ts +166 -9
- package/src/miru-index.ts +15 -6
- package/src/snippet.ts +6 -3
- package/src/token-count.ts +62 -0
- package/src/tokenizer/bert-wordpiece.ts +219 -0
- package/src/tokenizer/load.ts +14 -0
- package/src/types.ts +8 -0
- package/src/utils.ts +8 -1
- package/tokenizer/tokenizer.json +1 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
export interface TokenizerJson {
|
|
2
|
+
normalizer?: {
|
|
3
|
+
type?: string;
|
|
4
|
+
clean_text?: boolean;
|
|
5
|
+
handle_chinese_chars?: boolean;
|
|
6
|
+
strip_accents?: boolean | null;
|
|
7
|
+
lowercase?: boolean;
|
|
8
|
+
};
|
|
9
|
+
pre_tokenizer?: { type?: string };
|
|
10
|
+
model: {
|
|
11
|
+
type?: string;
|
|
12
|
+
vocab: Record<string, number>;
|
|
13
|
+
unk_token?: string;
|
|
14
|
+
continuing_subword_prefix?: string;
|
|
15
|
+
max_input_chars_per_word?: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BertWordPieceTokenizer {
|
|
20
|
+
encode(text: string): string[];
|
|
21
|
+
count(text: string): number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isControl(char: string): boolean {
|
|
25
|
+
const cp = char.codePointAt(0) ?? 0;
|
|
26
|
+
// TAB/LF/CR are whitespace in Bert clean_text, not stripped controls.
|
|
27
|
+
if (cp === 0x09 || cp === 0x0a || cp === 0x0d) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return cp === 0 || cp === 0xfffd || (cp >= 0x00 && cp <= 0x1f) || (cp >= 0x7f && cp <= 0x9f);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isWhitespace(char: string): boolean {
|
|
34
|
+
const cp = char.codePointAt(0) ?? 0;
|
|
35
|
+
return cp === 0x20 || cp === 0x09 || cp === 0x0a || cp === 0x0d;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isPunctuation(char: string): boolean {
|
|
39
|
+
const cp = char.codePointAt(0) ?? 0;
|
|
40
|
+
if (
|
|
41
|
+
(cp >= 33 && cp <= 47) ||
|
|
42
|
+
(cp >= 58 && cp <= 64) ||
|
|
43
|
+
(cp >= 91 && cp <= 96) ||
|
|
44
|
+
(cp >= 123 && cp <= 126)
|
|
45
|
+
) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return /\p{P}/u.test(char);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isChineseChar(cp: number): boolean {
|
|
52
|
+
return (
|
|
53
|
+
(cp >= 0x4e00 && cp <= 0x9fff) ||
|
|
54
|
+
(cp >= 0x3400 && cp <= 0x4dbf) ||
|
|
55
|
+
(cp >= 0x20000 && cp <= 0x2a6df) ||
|
|
56
|
+
(cp >= 0x2a700 && cp <= 0x2b73f) ||
|
|
57
|
+
(cp >= 0x2b740 && cp <= 0x2b81f) ||
|
|
58
|
+
(cp >= 0x2b820 && cp <= 0x2ceaf) ||
|
|
59
|
+
(cp >= 0xf900 && cp <= 0xfaff) ||
|
|
60
|
+
(cp >= 0x2f800 && cp <= 0x2fa1f)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cleanText(text: string): string {
|
|
65
|
+
const output: string[] = [];
|
|
66
|
+
for (const char of text) {
|
|
67
|
+
if (isControl(char)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
output.push(isWhitespace(char) ? " " : char);
|
|
71
|
+
}
|
|
72
|
+
return output.join("");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function tokenizeChineseChars(text: string): string {
|
|
76
|
+
const output: string[] = [];
|
|
77
|
+
for (const char of text) {
|
|
78
|
+
const cp = char.codePointAt(0) ?? 0;
|
|
79
|
+
if (isChineseChar(cp)) {
|
|
80
|
+
output.push(" ", char, " ");
|
|
81
|
+
} else {
|
|
82
|
+
output.push(char);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return output.join("");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function removeAccents(text: string): string {
|
|
89
|
+
return text.normalize("NFD").replace(/\p{M}/gu, "");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function whitespaceTokenize(text: string): string[] {
|
|
93
|
+
const trimmed = text.trim();
|
|
94
|
+
if (!trimmed) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
return trimmed.split(/\s+/);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function splitOnPunctuation(text: string): string[] {
|
|
101
|
+
const chars = [...text];
|
|
102
|
+
if (chars.length === 0) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const output: string[][] = [];
|
|
107
|
+
let startNewWord = true;
|
|
108
|
+
|
|
109
|
+
for (const char of chars) {
|
|
110
|
+
if (isPunctuation(char)) {
|
|
111
|
+
output.push([char]);
|
|
112
|
+
startNewWord = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (startNewWord) {
|
|
116
|
+
output.push([]);
|
|
117
|
+
startNewWord = false;
|
|
118
|
+
}
|
|
119
|
+
output[output.length - 1]?.push(char);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return output.map((parts) => parts.join("")).filter(Boolean);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function bertPreTokenize(text: string): string[] {
|
|
126
|
+
const words = whitespaceTokenize(text);
|
|
127
|
+
const splitTokens: string[] = [];
|
|
128
|
+
for (const word of words) {
|
|
129
|
+
splitTokens.push(...splitOnPunctuation(word));
|
|
130
|
+
}
|
|
131
|
+
return whitespaceTokenize(splitTokens.join(" "));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function wordpieceTokenize(
|
|
135
|
+
token: string,
|
|
136
|
+
vocab: Set<string>,
|
|
137
|
+
unkToken: string,
|
|
138
|
+
prefix: string,
|
|
139
|
+
maxInputCharsPerWord: number,
|
|
140
|
+
): string[] {
|
|
141
|
+
if (token.length === 0) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
if (token.length > maxInputCharsPerWord) {
|
|
145
|
+
return [unkToken];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const chars = [...token];
|
|
149
|
+
const subTokens: string[] = [];
|
|
150
|
+
let start = 0;
|
|
151
|
+
|
|
152
|
+
while (start < chars.length) {
|
|
153
|
+
let end = chars.length;
|
|
154
|
+
let curSubstr: string | null = null;
|
|
155
|
+
|
|
156
|
+
while (start < end) {
|
|
157
|
+
let substr = chars.slice(start, end).join("");
|
|
158
|
+
if (start > 0) {
|
|
159
|
+
substr = `${prefix}${substr}`;
|
|
160
|
+
}
|
|
161
|
+
if (vocab.has(substr)) {
|
|
162
|
+
curSubstr = substr;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
end -= 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!curSubstr) {
|
|
169
|
+
return [unkToken];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
subTokens.push(curSubstr);
|
|
173
|
+
start = end;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return subTokens;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createBertWordPieceTokenizer(json: TokenizerJson): BertWordPieceTokenizer {
|
|
180
|
+
const normalizer = json.normalizer ?? {};
|
|
181
|
+
const model = json.model;
|
|
182
|
+
const lowercase = normalizer.lowercase ?? true;
|
|
183
|
+
const stripAccents = normalizer.strip_accents ?? lowercase;
|
|
184
|
+
const vocab = new Set(Object.keys(model.vocab));
|
|
185
|
+
const unkToken = model.unk_token ?? "[UNK]";
|
|
186
|
+
const prefix = model.continuing_subword_prefix ?? "##";
|
|
187
|
+
const maxInputCharsPerWord = model.max_input_chars_per_word ?? 100;
|
|
188
|
+
const clean = normalizer.clean_text ?? true;
|
|
189
|
+
const handleChineseChars = normalizer.handle_chinese_chars ?? true;
|
|
190
|
+
|
|
191
|
+
function encode(text: string): string[] {
|
|
192
|
+
let normalized = text;
|
|
193
|
+
if (clean) {
|
|
194
|
+
normalized = cleanText(normalized);
|
|
195
|
+
}
|
|
196
|
+
if (handleChineseChars) {
|
|
197
|
+
normalized = tokenizeChineseChars(normalized);
|
|
198
|
+
}
|
|
199
|
+
if (lowercase) {
|
|
200
|
+
normalized = normalized.toLowerCase();
|
|
201
|
+
if (stripAccents) {
|
|
202
|
+
normalized = removeAccents(normalized);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const tokens: string[] = [];
|
|
207
|
+
for (const basic of bertPreTokenize(normalized)) {
|
|
208
|
+
tokens.push(...wordpieceTokenize(basic, vocab, unkToken, prefix, maxInputCharsPerWord));
|
|
209
|
+
}
|
|
210
|
+
return tokens;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
encode,
|
|
215
|
+
count(text: string): number {
|
|
216
|
+
return encode(text).length;
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
type BertWordPieceTokenizer,
|
|
4
|
+
createBertWordPieceTokenizer,
|
|
5
|
+
type TokenizerJson,
|
|
6
|
+
} from "./bert-wordpiece.ts";
|
|
7
|
+
|
|
8
|
+
export function loadTokenizerFromFile(path: string): BertWordPieceTokenizer {
|
|
9
|
+
const json = JSON.parse(readFileSync(path, "utf-8")) as TokenizerJson;
|
|
10
|
+
if (json.model?.type !== "WordPiece") {
|
|
11
|
+
throw new Error(`Unsupported tokenizer model type: ${json.model?.type ?? "unknown"}`);
|
|
12
|
+
}
|
|
13
|
+
return createBertWordPieceTokenizer(json);
|
|
14
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
export type ContentType = "code" | "docs" | "config";
|
|
2
2
|
|
|
3
|
+
/** Default index corpus: source code plus config (json/yaml/toml/…). */
|
|
4
|
+
export const DEFAULT_CONTENT_TYPES: readonly ContentType[] = ["code", "config"];
|
|
5
|
+
|
|
6
|
+
/** Mutable copy of {@link DEFAULT_CONTENT_TYPES} for API defaults. */
|
|
7
|
+
export function defaultContentTypes(): ContentType[] {
|
|
8
|
+
return [...DEFAULT_CONTENT_TYPES];
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
export interface Chunk {
|
|
4
12
|
content: string;
|
|
5
13
|
file_path: string;
|
package/src/utils.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join, relative, resolve, sep } from "node:path";
|
|
2
2
|
import { applySnippetsToResults, searchSnippetsEnabled } from "./snippet.ts";
|
|
3
3
|
import type { Chunk, ContentType, SearchResult } from "./types.ts";
|
|
4
|
-
import { chunkToDict } from "./types.ts";
|
|
4
|
+
import { chunkToDict, defaultContentTypes } from "./types.ts";
|
|
5
5
|
|
|
6
6
|
const GIT_URL_SCHEMES = [
|
|
7
7
|
"https://",
|
|
@@ -103,6 +103,10 @@ export type ExpandResults = {
|
|
|
103
103
|
export const DEFAULT_MCP_TOP_K = 3;
|
|
104
104
|
export const MAX_MCP_TOP_K = 10;
|
|
105
105
|
|
|
106
|
+
/** Extra chunks before/after the expand anchor — MCP `expand` tool defaults. */
|
|
107
|
+
export const DEFAULT_EXPAND_BEFORE = 1;
|
|
108
|
+
export const DEFAULT_EXPAND_AFTER = 1;
|
|
109
|
+
|
|
106
110
|
/** Clamp MCP top_k to a sane range; omit for the default. */
|
|
107
111
|
export function clampMcpTopK(topK?: number): number {
|
|
108
112
|
const value = topK ?? DEFAULT_MCP_TOP_K;
|
|
@@ -246,6 +250,9 @@ export function formatExpandResults(
|
|
|
246
250
|
}
|
|
247
251
|
|
|
248
252
|
export function resolveContent(raw: string[]): ContentType[] {
|
|
253
|
+
if (raw.length === 0) {
|
|
254
|
+
return defaultContentTypes();
|
|
255
|
+
}
|
|
249
256
|
if (raw.includes("all")) {
|
|
250
257
|
return ["code", "docs", "config"];
|
|
251
258
|
}
|