nano-brain 2026.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/AGENTS_SNIPPET.md +36 -0
- package/CHANGELOG.md +68 -0
- package/README.md +281 -0
- package/SKILL.md +153 -0
- package/bin/cli.js +18 -0
- package/index.html +929 -0
- package/nano-brain +4 -0
- package/opencode-mcp.json +9 -0
- package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/design.md +68 -0
- package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/proposal.md +27 -0
- package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/specs/mcp-integration-testing/spec.md +50 -0
- package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/specs/mcp-server/spec.md +40 -0
- package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/specs/search-pipeline/spec.md +29 -0
- package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/tasks.md +37 -0
- package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/design.md +111 -0
- package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/proposal.md +30 -0
- package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/specs/mcp-server/spec.md +33 -0
- package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/specs/storage-limits/spec.md +90 -0
- package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/specs/workspace-scoping/spec.md +66 -0
- package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/tasks.md +199 -0
- package/openspec/changes/codebase-indexing/.openspec.yaml +2 -0
- package/openspec/changes/codebase-indexing/design.md +169 -0
- package/openspec/changes/codebase-indexing/proposal.md +30 -0
- package/openspec/changes/codebase-indexing/specs/codebase-collection/spec.md +187 -0
- package/openspec/changes/codebase-indexing/specs/mcp-server/spec.md +36 -0
- package/openspec/changes/codebase-indexing/tasks.md +56 -0
- package/openspec/specs/mcp-integration-testing/spec.md +50 -0
- package/openspec/specs/mcp-server/spec.md +75 -0
- package/openspec/specs/search-pipeline/spec.md +29 -0
- package/openspec/specs/storage-limits/spec.md +94 -0
- package/openspec/specs/workspace-scoping/spec.md +70 -0
- package/package.json +34 -0
- package/site/build.js +66 -0
- package/site/partials/_api.html +83 -0
- package/site/partials/_compare.html +100 -0
- package/site/partials/_config.html +23 -0
- package/site/partials/_features.html +43 -0
- package/site/partials/_footer.html +6 -0
- package/site/partials/_hero.html +9 -0
- package/site/partials/_how-it-works.html +26 -0
- package/site/partials/_models.html +18 -0
- package/site/partials/_quick-start.html +15 -0
- package/site/partials/_stats.html +1 -0
- package/site/partials/_tech-stack.html +13 -0
- package/site/script.js +12 -0
- package/site/shell.html +44 -0
- package/site/styles.css +548 -0
- package/src/chunker.ts +427 -0
- package/src/codebase.ts +331 -0
- package/src/collections.ts +192 -0
- package/src/embeddings.ts +293 -0
- package/src/expansion.ts +79 -0
- package/src/harvester.ts +306 -0
- package/src/index.ts +503 -0
- package/src/reranker.ts +103 -0
- package/src/search.ts +294 -0
- package/src/server.ts +664 -0
- package/src/storage.ts +221 -0
- package/src/store.ts +623 -0
- package/src/types.ts +202 -0
- package/src/watcher.ts +384 -0
- package/test/chunker.test.ts +479 -0
- package/test/cli.test.ts +309 -0
- package/test/codebase-chunker.test.ts +446 -0
- package/test/codebase.test.ts +678 -0
- package/test/collections.test.ts +571 -0
- package/test/harvester.test.ts +636 -0
- package/test/integration.test.ts +150 -0
- package/test/llm.test.ts +322 -0
- package/test/search.test.ts +572 -0
- package/test/server.test.ts +541 -0
- package/test/storage.test.ts +302 -0
- package/test/store.test.ts +465 -0
- package/test/watcher.test.ts +656 -0
- package/test/workspace.test.ts +239 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +16 -0
package/src/chunker.ts
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import type { MemoryChunk, BreakPoint, CodeFenceRegion } from './types.js';
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
|
|
4
|
+
export interface ChunkOptions {
|
|
5
|
+
maxChunkSize?: number;
|
|
6
|
+
minChunkSize?: number;
|
|
7
|
+
overlap?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function findBreakPoints(content: string): BreakPoint[] {
|
|
11
|
+
const breakPoints: BreakPoint[] = [];
|
|
12
|
+
const lines = content.split('\n');
|
|
13
|
+
let pos = 0;
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16
|
+
const line = lines[i];
|
|
17
|
+
const lineNo = i + 1;
|
|
18
|
+
|
|
19
|
+
if (line.startsWith('# ')) {
|
|
20
|
+
breakPoints.push({ pos, score: 100, type: 'h1', lineNo });
|
|
21
|
+
} else if (line.startsWith('## ')) {
|
|
22
|
+
breakPoints.push({ pos, score: 90, type: 'h2', lineNo });
|
|
23
|
+
} else if (line.startsWith('### ')) {
|
|
24
|
+
breakPoints.push({ pos, score: 80, type: 'h3', lineNo });
|
|
25
|
+
} else if (line.startsWith('#### ') || line.startsWith('##### ') || line.startsWith('###### ')) {
|
|
26
|
+
breakPoints.push({ pos, score: 70, type: 'h4-h6', lineNo });
|
|
27
|
+
} else if (line.startsWith('```')) {
|
|
28
|
+
breakPoints.push({ pos, score: 80, type: 'code-fence', lineNo });
|
|
29
|
+
} else if (line.trim() === '---' || line.trim() === '***' || line.trim() === '___') {
|
|
30
|
+
breakPoints.push({ pos, score: 60, type: 'hr', lineNo });
|
|
31
|
+
} else if (line.trim() === '') {
|
|
32
|
+
breakPoints.push({ pos, score: 20, type: 'blank', lineNo });
|
|
33
|
+
} else if (/^(\s*)([-*+]|\d+\.)\s/.test(line)) {
|
|
34
|
+
breakPoints.push({ pos, score: 5, type: 'list', lineNo });
|
|
35
|
+
} else {
|
|
36
|
+
breakPoints.push({ pos, score: 1, type: 'newline', lineNo });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pos += line.length + 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return breakPoints;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function findCodeFences(content: string): CodeFenceRegion[] {
|
|
46
|
+
const regions: CodeFenceRegion[] = [];
|
|
47
|
+
const lines = content.split('\n');
|
|
48
|
+
let pos = 0;
|
|
49
|
+
let fenceStart: number | null = null;
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < lines.length; i++) {
|
|
52
|
+
const line = lines[i];
|
|
53
|
+
|
|
54
|
+
if (line.startsWith('```')) {
|
|
55
|
+
if (fenceStart === null) {
|
|
56
|
+
fenceStart = pos;
|
|
57
|
+
} else {
|
|
58
|
+
regions.push({ start: fenceStart, end: pos + line.length });
|
|
59
|
+
fenceStart = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pos += line.length + 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (fenceStart !== null) {
|
|
67
|
+
regions.push({ start: fenceStart, end: content.length });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return regions;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function findBestCutoff(
|
|
74
|
+
breakPoints: BreakPoint[],
|
|
75
|
+
targetPos: number,
|
|
76
|
+
windowSize: number,
|
|
77
|
+
codeFences: CodeFenceRegion[]
|
|
78
|
+
): number {
|
|
79
|
+
const windowStart = targetPos - windowSize;
|
|
80
|
+
const windowEnd = targetPos + windowSize;
|
|
81
|
+
|
|
82
|
+
const candidateBreaks = breakPoints.filter(
|
|
83
|
+
bp => bp.pos >= windowStart && bp.pos <= windowEnd
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (candidateBreaks.length === 0) {
|
|
87
|
+
const insideTargetFence = codeFences.some(
|
|
88
|
+
fence => targetPos >= fence.start && targetPos < fence.end
|
|
89
|
+
);
|
|
90
|
+
if (insideTargetFence) {
|
|
91
|
+
const fence = codeFences.find(f => targetPos >= f.start && targetPos < f.end);
|
|
92
|
+
if (fence) {
|
|
93
|
+
return fence.end;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return targetPos;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let bestBreak = candidateBreaks[0];
|
|
100
|
+
let bestScore = -1;
|
|
101
|
+
|
|
102
|
+
for (const bp of candidateBreaks) {
|
|
103
|
+
const insideFence = codeFences.some(
|
|
104
|
+
fence => bp.pos >= fence.start && bp.pos < fence.end
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (insideFence) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const distance = Math.abs(bp.pos - targetPos);
|
|
112
|
+
const distancePenalty = Math.pow(distance / windowSize, 2) * 0.7;
|
|
113
|
+
const finalScore = bp.score * (1 - distancePenalty);
|
|
114
|
+
|
|
115
|
+
if (finalScore > bestScore) {
|
|
116
|
+
bestScore = finalScore;
|
|
117
|
+
bestBreak = bp;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (bestScore === -1) {
|
|
122
|
+
const insideTargetFence = codeFences.some(
|
|
123
|
+
fence => targetPos >= fence.start && targetPos < fence.end
|
|
124
|
+
);
|
|
125
|
+
if (insideTargetFence) {
|
|
126
|
+
const fence = codeFences.find(f => targetPos >= f.start && targetPos < f.end);
|
|
127
|
+
if (fence) {
|
|
128
|
+
return fence.end;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return targetPos;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return bestBreak.pos;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function chunkMarkdown(
|
|
138
|
+
content: string,
|
|
139
|
+
hash: string,
|
|
140
|
+
options?: ChunkOptions
|
|
141
|
+
): MemoryChunk[] {
|
|
142
|
+
const maxChunkSize = options?.maxChunkSize ?? 3600;
|
|
143
|
+
const minChunkSize = options?.minChunkSize ?? 200;
|
|
144
|
+
const overlap = options?.overlap ?? 540;
|
|
145
|
+
const windowSize = 800;
|
|
146
|
+
|
|
147
|
+
if (content.length <= maxChunkSize) {
|
|
148
|
+
return [{
|
|
149
|
+
hash,
|
|
150
|
+
seq: 0,
|
|
151
|
+
pos: 0,
|
|
152
|
+
text: content,
|
|
153
|
+
startLine: 1,
|
|
154
|
+
endLine: content.split('\n').length,
|
|
155
|
+
}];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const breakPoints = findBreakPoints(content);
|
|
159
|
+
const codeFences = findCodeFences(content);
|
|
160
|
+
const chunks: MemoryChunk[] = [];
|
|
161
|
+
let currentPos = 0;
|
|
162
|
+
let seq = 0;
|
|
163
|
+
let runningLineCount = 1;
|
|
164
|
+
while (currentPos < content.length) {
|
|
165
|
+
const targetPos = currentPos + maxChunkSize;
|
|
166
|
+
let cutoff: number;
|
|
167
|
+
if (targetPos >= content.length) {
|
|
168
|
+
cutoff = content.length;
|
|
169
|
+
} else {
|
|
170
|
+
cutoff = findBestCutoff(breakPoints, targetPos, windowSize, codeFences);
|
|
171
|
+
}
|
|
172
|
+
const chunkText = content.slice(currentPos, cutoff);
|
|
173
|
+
const startLine = runningLineCount;
|
|
174
|
+
const endLine = startLine + chunkText.split('\n').length - 1;
|
|
175
|
+
chunks.push({
|
|
176
|
+
hash,
|
|
177
|
+
seq,
|
|
178
|
+
pos: currentPos,
|
|
179
|
+
text: chunkText,
|
|
180
|
+
startLine,
|
|
181
|
+
endLine,
|
|
182
|
+
});
|
|
183
|
+
if (cutoff >= content.length) {
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
const nextPos = cutoff - overlap;
|
|
187
|
+
const prevPos = currentPos;
|
|
188
|
+
if (nextPos <= currentPos) {
|
|
189
|
+
currentPos = cutoff;
|
|
190
|
+
} else {
|
|
191
|
+
currentPos = nextPos;
|
|
192
|
+
}
|
|
193
|
+
const advancedSlice = content.slice(prevPos, currentPos);
|
|
194
|
+
runningLineCount += (advancedSlice.match(/\n/g) || []).length;
|
|
195
|
+
seq++;
|
|
196
|
+
}
|
|
197
|
+
return chunks;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
const EXTENSION_TO_LANGUAGE: Record<string, string> = {
|
|
202
|
+
'.ts': 'typescript',
|
|
203
|
+
'.tsx': 'typescript',
|
|
204
|
+
'.js': 'javascript',
|
|
205
|
+
'.jsx': 'javascript',
|
|
206
|
+
'.mjs': 'javascript',
|
|
207
|
+
'.cjs': 'javascript',
|
|
208
|
+
'.py': 'python',
|
|
209
|
+
'.pyi': 'python',
|
|
210
|
+
'.go': 'go',
|
|
211
|
+
'.rs': 'rust',
|
|
212
|
+
'.java': 'java',
|
|
213
|
+
'.kt': 'kotlin',
|
|
214
|
+
'.kts': 'kotlin',
|
|
215
|
+
'.rb': 'ruby',
|
|
216
|
+
'.erb': 'ruby',
|
|
217
|
+
'.c': 'c',
|
|
218
|
+
'.h': 'c',
|
|
219
|
+
'.cpp': 'cpp',
|
|
220
|
+
'.hpp': 'cpp',
|
|
221
|
+
'.cc': 'cpp',
|
|
222
|
+
'.cs': 'csharp',
|
|
223
|
+
'.swift': 'swift',
|
|
224
|
+
'.php': 'php',
|
|
225
|
+
'.sh': 'bash',
|
|
226
|
+
'.bash': 'bash',
|
|
227
|
+
'.zsh': 'zsh',
|
|
228
|
+
'.json': 'json',
|
|
229
|
+
'.yaml': 'yaml',
|
|
230
|
+
'.yml': 'yaml',
|
|
231
|
+
'.toml': 'toml',
|
|
232
|
+
'.md': 'markdown',
|
|
233
|
+
'.sql': 'sql',
|
|
234
|
+
'.html': 'html',
|
|
235
|
+
'.css': 'css',
|
|
236
|
+
'.scss': 'scss',
|
|
237
|
+
'.less': 'less',
|
|
238
|
+
'.vue': 'vue',
|
|
239
|
+
'.svelte': 'svelte',
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function inferLanguage(filePath: string): string {
|
|
243
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
244
|
+
return EXTENSION_TO_LANGUAGE[ext] || 'text'
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const FUNCTION_DEF_PATTERNS = [
|
|
248
|
+
/^(export\s+)?(async\s+)?function\s+/,
|
|
249
|
+
/^(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?\(/,
|
|
250
|
+
/^(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?function/,
|
|
251
|
+
/^(export\s+)?class\s+/,
|
|
252
|
+
/^(export\s+)?(interface|type)\s+/,
|
|
253
|
+
/^def\s+\w+\s*\(/,
|
|
254
|
+
/^class\s+\w+/,
|
|
255
|
+
/^func\s+\w+\s*\(/,
|
|
256
|
+
/^fn\s+\w+/,
|
|
257
|
+
/^pub\s+(fn|struct|enum|trait)\s+/,
|
|
258
|
+
/^(public|private|protected)?\s*(static)?\s*(async)?\s*\w+\s*\([^)]*\)\s*{/,
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
const IMPORT_EXPORT_PATTERNS = [
|
|
262
|
+
/^import\s+/,
|
|
263
|
+
/^export\s+/,
|
|
264
|
+
/^from\s+/,
|
|
265
|
+
/^require\s*\(/,
|
|
266
|
+
/^module\.exports/,
|
|
267
|
+
/^package\s+/,
|
|
268
|
+
/^use\s+/,
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
export function findSourceCodeBreakPoints(content: string): BreakPoint[] {
|
|
272
|
+
const breakPoints: BreakPoint[] = []
|
|
273
|
+
const lines = content.split('\n')
|
|
274
|
+
let pos = 0
|
|
275
|
+
let prevLineBlank = false
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i < lines.length; i++) {
|
|
278
|
+
const line = lines[i]
|
|
279
|
+
const lineNo = i + 1
|
|
280
|
+
const trimmed = line.trim()
|
|
281
|
+
|
|
282
|
+
if (trimmed === '') {
|
|
283
|
+
if (prevLineBlank) {
|
|
284
|
+
breakPoints.push({ pos, score: 90, type: 'double-blank', lineNo })
|
|
285
|
+
} else {
|
|
286
|
+
breakPoints.push({ pos, score: 40, type: 'blank', lineNo })
|
|
287
|
+
}
|
|
288
|
+
prevLineBlank = true
|
|
289
|
+
} else {
|
|
290
|
+
prevLineBlank = false
|
|
291
|
+
|
|
292
|
+
let matched = false
|
|
293
|
+
|
|
294
|
+
for (const pattern of FUNCTION_DEF_PATTERNS) {
|
|
295
|
+
if (pattern.test(trimmed)) {
|
|
296
|
+
breakPoints.push({ pos, score: 80, type: 'function-def', lineNo })
|
|
297
|
+
matched = true
|
|
298
|
+
break
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!matched) {
|
|
303
|
+
for (const pattern of IMPORT_EXPORT_PATTERNS) {
|
|
304
|
+
if (pattern.test(trimmed)) {
|
|
305
|
+
breakPoints.push({ pos, score: 60, type: 'import-export', lineNo })
|
|
306
|
+
matched = true
|
|
307
|
+
break
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!matched) {
|
|
313
|
+
breakPoints.push({ pos, score: 1, type: 'line', lineNo })
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
pos += line.length + 1
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return breakPoints
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function chunkSourceCode(
|
|
324
|
+
content: string,
|
|
325
|
+
hash: string,
|
|
326
|
+
filePath: string,
|
|
327
|
+
workspaceRoot: string,
|
|
328
|
+
options?: ChunkOptions
|
|
329
|
+
): MemoryChunk[] {
|
|
330
|
+
const maxChunkSize = options?.maxChunkSize ?? 3600
|
|
331
|
+
const overlap = options?.overlap ?? 540
|
|
332
|
+
const windowSize = 800
|
|
333
|
+
|
|
334
|
+
const relativePath = path.relative(workspaceRoot, filePath)
|
|
335
|
+
const language = inferLanguage(filePath)
|
|
336
|
+
|
|
337
|
+
const createMetadataHeader = (startLine: number, endLine: number): string => {
|
|
338
|
+
return `File: ${relativePath}\nLanguage: ${language}\nLines: ${startLine}-${endLine}\n\n`
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (content.length <= maxChunkSize) {
|
|
342
|
+
const lineCount = content.split('\n').length
|
|
343
|
+
const header = createMetadataHeader(1, lineCount)
|
|
344
|
+
return [{
|
|
345
|
+
hash,
|
|
346
|
+
seq: 0,
|
|
347
|
+
pos: 0,
|
|
348
|
+
text: header + content,
|
|
349
|
+
startLine: 1,
|
|
350
|
+
endLine: lineCount,
|
|
351
|
+
}]
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const breakPoints = findSourceCodeBreakPoints(content)
|
|
355
|
+
const chunks: MemoryChunk[] = []
|
|
356
|
+
let currentPos = 0
|
|
357
|
+
let seq = 0
|
|
358
|
+
let runningLineCount = 1
|
|
359
|
+
while (currentPos < content.length) {
|
|
360
|
+
const targetPos = currentPos + maxChunkSize
|
|
361
|
+
let cutoff: number
|
|
362
|
+
if (targetPos >= content.length) {
|
|
363
|
+
cutoff = content.length
|
|
364
|
+
} else {
|
|
365
|
+
cutoff = findBestSourceCodeCutoff(breakPoints, targetPos, windowSize)
|
|
366
|
+
}
|
|
367
|
+
const chunkText = content.slice(currentPos, cutoff)
|
|
368
|
+
const startLine = runningLineCount
|
|
369
|
+
const endLine = startLine + chunkText.split('\n').length - 1
|
|
370
|
+
const header = createMetadataHeader(startLine, endLine)
|
|
371
|
+
chunks.push({
|
|
372
|
+
hash,
|
|
373
|
+
seq,
|
|
374
|
+
pos: currentPos,
|
|
375
|
+
text: header + chunkText,
|
|
376
|
+
startLine,
|
|
377
|
+
endLine,
|
|
378
|
+
})
|
|
379
|
+
if (cutoff >= content.length) {
|
|
380
|
+
break
|
|
381
|
+
}
|
|
382
|
+
const nextPos = cutoff - overlap
|
|
383
|
+
const prevPos = currentPos
|
|
384
|
+
if (nextPos <= currentPos) {
|
|
385
|
+
currentPos = cutoff
|
|
386
|
+
} else {
|
|
387
|
+
currentPos = nextPos
|
|
388
|
+
}
|
|
389
|
+
const advancedSlice = content.slice(prevPos, currentPos)
|
|
390
|
+
runningLineCount += (advancedSlice.match(/\n/g) || []).length
|
|
391
|
+
seq++
|
|
392
|
+
}
|
|
393
|
+
return chunks
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function findBestSourceCodeCutoff(
|
|
397
|
+
breakPoints: BreakPoint[],
|
|
398
|
+
targetPos: number,
|
|
399
|
+
windowSize: number
|
|
400
|
+
): number {
|
|
401
|
+
const windowStart = targetPos - windowSize
|
|
402
|
+
const windowEnd = targetPos + windowSize
|
|
403
|
+
|
|
404
|
+
const candidateBreaks = breakPoints.filter(
|
|
405
|
+
bp => bp.pos >= windowStart && bp.pos <= windowEnd
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
if (candidateBreaks.length === 0) {
|
|
409
|
+
return targetPos
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let bestBreak = candidateBreaks[0]
|
|
413
|
+
let bestScore = -1
|
|
414
|
+
|
|
415
|
+
for (const bp of candidateBreaks) {
|
|
416
|
+
const distance = Math.abs(bp.pos - targetPos)
|
|
417
|
+
const distancePenalty = Math.pow(distance / windowSize, 2) * 0.7
|
|
418
|
+
const finalScore = bp.score * (1 - distancePenalty)
|
|
419
|
+
|
|
420
|
+
if (finalScore > bestScore) {
|
|
421
|
+
bestScore = finalScore
|
|
422
|
+
bestBreak = bp
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return bestBreak.pos
|
|
427
|
+
}
|