@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.
@@ -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
  }