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.
Files changed (79) hide show
  1. package/AGENTS_SNIPPET.md +36 -0
  2. package/CHANGELOG.md +68 -0
  3. package/README.md +281 -0
  4. package/SKILL.md +153 -0
  5. package/bin/cli.js +18 -0
  6. package/index.html +929 -0
  7. package/nano-brain +4 -0
  8. package/opencode-mcp.json +9 -0
  9. package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/.openspec.yaml +2 -0
  10. package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/design.md +68 -0
  11. package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/proposal.md +27 -0
  12. package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/specs/mcp-integration-testing/spec.md +50 -0
  13. package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/specs/mcp-server/spec.md +40 -0
  14. package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/specs/search-pipeline/spec.md +29 -0
  15. package/openspec/changes/archive/2026-02-16-fix-mcp-server-bugs/tasks.md +37 -0
  16. package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/.openspec.yaml +2 -0
  17. package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/design.md +111 -0
  18. package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/proposal.md +30 -0
  19. package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/specs/mcp-server/spec.md +33 -0
  20. package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/specs/storage-limits/spec.md +90 -0
  21. package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/specs/workspace-scoping/spec.md +66 -0
  22. package/openspec/changes/archive/2026-02-23-workspace-scoped-memory-and-storage-limits/tasks.md +199 -0
  23. package/openspec/changes/codebase-indexing/.openspec.yaml +2 -0
  24. package/openspec/changes/codebase-indexing/design.md +169 -0
  25. package/openspec/changes/codebase-indexing/proposal.md +30 -0
  26. package/openspec/changes/codebase-indexing/specs/codebase-collection/spec.md +187 -0
  27. package/openspec/changes/codebase-indexing/specs/mcp-server/spec.md +36 -0
  28. package/openspec/changes/codebase-indexing/tasks.md +56 -0
  29. package/openspec/specs/mcp-integration-testing/spec.md +50 -0
  30. package/openspec/specs/mcp-server/spec.md +75 -0
  31. package/openspec/specs/search-pipeline/spec.md +29 -0
  32. package/openspec/specs/storage-limits/spec.md +94 -0
  33. package/openspec/specs/workspace-scoping/spec.md +70 -0
  34. package/package.json +34 -0
  35. package/site/build.js +66 -0
  36. package/site/partials/_api.html +83 -0
  37. package/site/partials/_compare.html +100 -0
  38. package/site/partials/_config.html +23 -0
  39. package/site/partials/_features.html +43 -0
  40. package/site/partials/_footer.html +6 -0
  41. package/site/partials/_hero.html +9 -0
  42. package/site/partials/_how-it-works.html +26 -0
  43. package/site/partials/_models.html +18 -0
  44. package/site/partials/_quick-start.html +15 -0
  45. package/site/partials/_stats.html +1 -0
  46. package/site/partials/_tech-stack.html +13 -0
  47. package/site/script.js +12 -0
  48. package/site/shell.html +44 -0
  49. package/site/styles.css +548 -0
  50. package/src/chunker.ts +427 -0
  51. package/src/codebase.ts +331 -0
  52. package/src/collections.ts +192 -0
  53. package/src/embeddings.ts +293 -0
  54. package/src/expansion.ts +79 -0
  55. package/src/harvester.ts +306 -0
  56. package/src/index.ts +503 -0
  57. package/src/reranker.ts +103 -0
  58. package/src/search.ts +294 -0
  59. package/src/server.ts +664 -0
  60. package/src/storage.ts +221 -0
  61. package/src/store.ts +623 -0
  62. package/src/types.ts +202 -0
  63. package/src/watcher.ts +384 -0
  64. package/test/chunker.test.ts +479 -0
  65. package/test/cli.test.ts +309 -0
  66. package/test/codebase-chunker.test.ts +446 -0
  67. package/test/codebase.test.ts +678 -0
  68. package/test/collections.test.ts +571 -0
  69. package/test/harvester.test.ts +636 -0
  70. package/test/integration.test.ts +150 -0
  71. package/test/llm.test.ts +322 -0
  72. package/test/search.test.ts +572 -0
  73. package/test/server.test.ts +541 -0
  74. package/test/storage.test.ts +302 -0
  75. package/test/store.test.ts +465 -0
  76. package/test/watcher.test.ts +656 -0
  77. package/test/workspace.test.ts +239 -0
  78. package/tsconfig.json +19 -0
  79. package/vitest.config.ts +16 -0
@@ -0,0 +1,446 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { inferLanguage, findSourceCodeBreakPoints, chunkSourceCode } from '../src/chunker.js';
3
+
4
+ describe('inferLanguage', () => {
5
+ it('should detect TypeScript from .ts extension', () => {
6
+ expect(inferLanguage('/path/to/file.ts')).toBe('typescript');
7
+ });
8
+
9
+ it('should detect TypeScript from .tsx extension', () => {
10
+ expect(inferLanguage('/path/to/component.tsx')).toBe('typescript');
11
+ });
12
+
13
+ it('should detect JavaScript from .js extension', () => {
14
+ expect(inferLanguage('/path/to/file.js')).toBe('javascript');
15
+ });
16
+
17
+ it('should detect JavaScript from .jsx extension', () => {
18
+ expect(inferLanguage('/path/to/component.jsx')).toBe('javascript');
19
+ });
20
+
21
+ it('should detect JavaScript from .mjs extension', () => {
22
+ expect(inferLanguage('/path/to/module.mjs')).toBe('javascript');
23
+ });
24
+
25
+ it('should detect JavaScript from .cjs extension', () => {
26
+ expect(inferLanguage('/path/to/module.cjs')).toBe('javascript');
27
+ });
28
+
29
+ it('should detect Python from .py extension', () => {
30
+ expect(inferLanguage('/path/to/script.py')).toBe('python');
31
+ });
32
+
33
+ it('should detect Python from .pyi extension', () => {
34
+ expect(inferLanguage('/path/to/types.pyi')).toBe('python');
35
+ });
36
+
37
+ it('should detect Go from .go extension', () => {
38
+ expect(inferLanguage('/path/to/main.go')).toBe('go');
39
+ });
40
+
41
+ it('should detect Rust from .rs extension', () => {
42
+ expect(inferLanguage('/path/to/lib.rs')).toBe('rust');
43
+ });
44
+
45
+ it('should detect Java from .java extension', () => {
46
+ expect(inferLanguage('/path/to/Main.java')).toBe('java');
47
+ });
48
+
49
+ it('should detect Kotlin from .kt extension', () => {
50
+ expect(inferLanguage('/path/to/Main.kt')).toBe('kotlin');
51
+ });
52
+
53
+ it('should detect Ruby from .rb extension', () => {
54
+ expect(inferLanguage('/path/to/app.rb')).toBe('ruby');
55
+ });
56
+
57
+ it('should detect C from .c extension', () => {
58
+ expect(inferLanguage('/path/to/main.c')).toBe('c');
59
+ });
60
+
61
+ it('should detect C from .h extension', () => {
62
+ expect(inferLanguage('/path/to/header.h')).toBe('c');
63
+ });
64
+
65
+ it('should detect C++ from .cpp extension', () => {
66
+ expect(inferLanguage('/path/to/main.cpp')).toBe('cpp');
67
+ });
68
+
69
+ it('should detect C# from .cs extension', () => {
70
+ expect(inferLanguage('/path/to/Program.cs')).toBe('csharp');
71
+ });
72
+
73
+ it('should detect Swift from .swift extension', () => {
74
+ expect(inferLanguage('/path/to/App.swift')).toBe('swift');
75
+ });
76
+
77
+ it('should detect PHP from .php extension', () => {
78
+ expect(inferLanguage('/path/to/index.php')).toBe('php');
79
+ });
80
+
81
+ it('should detect Bash from .sh extension', () => {
82
+ expect(inferLanguage('/path/to/script.sh')).toBe('bash');
83
+ });
84
+
85
+ it('should detect JSON from .json extension', () => {
86
+ expect(inferLanguage('/path/to/config.json')).toBe('json');
87
+ });
88
+
89
+ it('should detect YAML from .yaml extension', () => {
90
+ expect(inferLanguage('/path/to/config.yaml')).toBe('yaml');
91
+ });
92
+
93
+ it('should detect YAML from .yml extension', () => {
94
+ expect(inferLanguage('/path/to/config.yml')).toBe('yaml');
95
+ });
96
+
97
+ it('should detect Markdown from .md extension', () => {
98
+ expect(inferLanguage('/path/to/README.md')).toBe('markdown');
99
+ });
100
+
101
+ it('should detect SQL from .sql extension', () => {
102
+ expect(inferLanguage('/path/to/query.sql')).toBe('sql');
103
+ });
104
+
105
+ it('should detect HTML from .html extension', () => {
106
+ expect(inferLanguage('/path/to/index.html')).toBe('html');
107
+ });
108
+
109
+ it('should detect CSS from .css extension', () => {
110
+ expect(inferLanguage('/path/to/styles.css')).toBe('css');
111
+ });
112
+
113
+ it('should detect Vue from .vue extension', () => {
114
+ expect(inferLanguage('/path/to/Component.vue')).toBe('vue');
115
+ });
116
+
117
+ it('should detect Svelte from .svelte extension', () => {
118
+ expect(inferLanguage('/path/to/Component.svelte')).toBe('svelte');
119
+ });
120
+
121
+ it('should return text for unknown extension', () => {
122
+ expect(inferLanguage('/path/to/file.xyz')).toBe('text');
123
+ });
124
+
125
+ it('should return text for no extension', () => {
126
+ expect(inferLanguage('/path/to/Makefile')).toBe('text');
127
+ });
128
+
129
+ it('should be case insensitive', () => {
130
+ expect(inferLanguage('/path/to/file.TS')).toBe('typescript');
131
+ expect(inferLanguage('/path/to/file.PY')).toBe('python');
132
+ });
133
+ });
134
+
135
+ describe('findSourceCodeBreakPoints', () => {
136
+ it('should detect blank lines with score 40', () => {
137
+ const content = 'line1\n\nline3';
138
+ const breakPoints = findSourceCodeBreakPoints(content);
139
+
140
+ const blank = breakPoints.find(bp => bp.type === 'blank');
141
+ expect(blank).toBeDefined();
142
+ expect(blank?.score).toBe(40);
143
+ expect(blank?.lineNo).toBe(2);
144
+ });
145
+
146
+ it('should detect double blank lines with score 90', () => {
147
+ const content = 'line1\n\n\nline4';
148
+ const breakPoints = findSourceCodeBreakPoints(content);
149
+
150
+ const doubleBlank = breakPoints.find(bp => bp.type === 'double-blank');
151
+ expect(doubleBlank).toBeDefined();
152
+ expect(doubleBlank?.score).toBe(90);
153
+ });
154
+
155
+ it('should detect function definitions with score 80', () => {
156
+ const content = 'function myFunc() {\n return 1;\n}';
157
+ const breakPoints = findSourceCodeBreakPoints(content);
158
+
159
+ const funcDef = breakPoints.find(bp => bp.type === 'function-def');
160
+ expect(funcDef).toBeDefined();
161
+ expect(funcDef?.score).toBe(80);
162
+ expect(funcDef?.lineNo).toBe(1);
163
+ });
164
+
165
+ it('should detect export function definitions', () => {
166
+ const content = 'export function myFunc() {}';
167
+ const breakPoints = findSourceCodeBreakPoints(content);
168
+
169
+ const funcDef = breakPoints.find(bp => bp.type === 'function-def');
170
+ expect(funcDef).toBeDefined();
171
+ });
172
+
173
+ it('should detect async function definitions', () => {
174
+ const content = 'async function fetchData() {}';
175
+ const breakPoints = findSourceCodeBreakPoints(content);
176
+
177
+ const funcDef = breakPoints.find(bp => bp.type === 'function-def');
178
+ expect(funcDef).toBeDefined();
179
+ });
180
+
181
+ it('should detect export async function definitions', () => {
182
+ const content = 'export async function fetchData() {}';
183
+ const breakPoints = findSourceCodeBreakPoints(content);
184
+
185
+ const funcDef = breakPoints.find(bp => bp.type === 'function-def');
186
+ expect(funcDef).toBeDefined();
187
+ });
188
+
189
+ it('should detect arrow function assignments', () => {
190
+ const content = 'const myFunc = () => {};';
191
+ const breakPoints = findSourceCodeBreakPoints(content);
192
+
193
+ const funcDef = breakPoints.find(bp => bp.type === 'function-def');
194
+ expect(funcDef).toBeDefined();
195
+ });
196
+
197
+ it('should detect class definitions', () => {
198
+ const content = 'class MyClass {\n constructor() {}\n}';
199
+ const breakPoints = findSourceCodeBreakPoints(content);
200
+
201
+ const classDef = breakPoints.find(bp => bp.type === 'function-def');
202
+ expect(classDef).toBeDefined();
203
+ });
204
+
205
+ it('should detect export class definitions', () => {
206
+ const content = 'export class MyClass {}';
207
+ const breakPoints = findSourceCodeBreakPoints(content);
208
+
209
+ const classDef = breakPoints.find(bp => bp.type === 'function-def');
210
+ expect(classDef).toBeDefined();
211
+ });
212
+
213
+ it('should detect interface definitions', () => {
214
+ const content = 'interface MyInterface {\n prop: string;\n}';
215
+ const breakPoints = findSourceCodeBreakPoints(content);
216
+
217
+ const interfaceDef = breakPoints.find(bp => bp.type === 'function-def');
218
+ expect(interfaceDef).toBeDefined();
219
+ });
220
+
221
+ it('should detect type definitions', () => {
222
+ const content = 'type MyType = string | number;';
223
+ const breakPoints = findSourceCodeBreakPoints(content);
224
+
225
+ const typeDef = breakPoints.find(bp => bp.type === 'function-def');
226
+ expect(typeDef).toBeDefined();
227
+ });
228
+
229
+ it('should detect Python def statements', () => {
230
+ const content = 'def my_function():\n pass';
231
+ const breakPoints = findSourceCodeBreakPoints(content);
232
+
233
+ const funcDef = breakPoints.find(bp => bp.type === 'function-def');
234
+ expect(funcDef).toBeDefined();
235
+ });
236
+
237
+ it('should detect Python class statements', () => {
238
+ const content = 'class MyClass:\n pass';
239
+ const breakPoints = findSourceCodeBreakPoints(content);
240
+
241
+ const classDef = breakPoints.find(bp => bp.type === 'function-def');
242
+ expect(classDef).toBeDefined();
243
+ });
244
+
245
+ it('should detect Go func statements', () => {
246
+ const content = 'func main() {\n}';
247
+ const breakPoints = findSourceCodeBreakPoints(content);
248
+
249
+ const funcDef = breakPoints.find(bp => bp.type === 'function-def');
250
+ expect(funcDef).toBeDefined();
251
+ });
252
+
253
+ it('should detect Rust fn statements', () => {
254
+ const content = 'fn main() {\n}';
255
+ const breakPoints = findSourceCodeBreakPoints(content);
256
+
257
+ const funcDef = breakPoints.find(bp => bp.type === 'function-def');
258
+ expect(funcDef).toBeDefined();
259
+ });
260
+
261
+ it('should detect Rust pub fn statements', () => {
262
+ const content = 'pub fn my_function() {}';
263
+ const breakPoints = findSourceCodeBreakPoints(content);
264
+
265
+ const funcDef = breakPoints.find(bp => bp.type === 'function-def');
266
+ expect(funcDef).toBeDefined();
267
+ });
268
+
269
+ it('should detect import statements with score 60', () => {
270
+ const content = "import { foo } from 'bar';";
271
+ const breakPoints = findSourceCodeBreakPoints(content);
272
+
273
+ const importStmt = breakPoints.find(bp => bp.type === 'import-export');
274
+ expect(importStmt).toBeDefined();
275
+ expect(importStmt?.score).toBe(60);
276
+ });
277
+
278
+ it('should detect export statements', () => {
279
+ const content = "export { foo };";
280
+ const breakPoints = findSourceCodeBreakPoints(content);
281
+
282
+ const exportStmt = breakPoints.find(bp => bp.type === 'import-export');
283
+ expect(exportStmt).toBeDefined();
284
+ });
285
+
286
+ it('should detect require statements at line start', () => {
287
+ const content = "require('bar');";
288
+ const breakPoints = findSourceCodeBreakPoints(content);
289
+ const requireStmt = breakPoints.find(bp => bp.type === 'import-export');
290
+ expect(requireStmt).toBeDefined();
291
+ });
292
+
293
+ it('should detect module.exports', () => {
294
+ const content = 'module.exports = foo;';
295
+ const breakPoints = findSourceCodeBreakPoints(content);
296
+
297
+ const moduleExports = breakPoints.find(bp => bp.type === 'import-export');
298
+ expect(moduleExports).toBeDefined();
299
+ });
300
+
301
+ it('should detect Rust use statements', () => {
302
+ const content = 'use std::io;';
303
+ const breakPoints = findSourceCodeBreakPoints(content);
304
+
305
+ const useStmt = breakPoints.find(bp => bp.type === 'import-export');
306
+ expect(useStmt).toBeDefined();
307
+ });
308
+
309
+ it('should detect regular lines with score 1', () => {
310
+ const content = 'const x = 1;';
311
+ const breakPoints = findSourceCodeBreakPoints(content);
312
+
313
+ const line = breakPoints.find(bp => bp.type === 'line');
314
+ expect(line).toBeDefined();
315
+ expect(line?.score).toBe(1);
316
+ });
317
+
318
+ it('should calculate correct positions', () => {
319
+ const content = 'line1\nline2\nline3';
320
+ const breakPoints = findSourceCodeBreakPoints(content);
321
+
322
+ expect(breakPoints[0].pos).toBe(0);
323
+ expect(breakPoints[1].pos).toBe(6);
324
+ expect(breakPoints[2].pos).toBe(12);
325
+ });
326
+
327
+ it('should assign correct line numbers', () => {
328
+ const content = 'line1\nline2\nline3';
329
+ const breakPoints = findSourceCodeBreakPoints(content);
330
+
331
+ expect(breakPoints[0].lineNo).toBe(1);
332
+ expect(breakPoints[1].lineNo).toBe(2);
333
+ expect(breakPoints[2].lineNo).toBe(3);
334
+ });
335
+
336
+ it('should handle empty content', () => {
337
+ const content = '';
338
+ const breakPoints = findSourceCodeBreakPoints(content);
339
+
340
+ expect(breakPoints.length).toBe(1);
341
+ expect(breakPoints[0].type).toBe('blank');
342
+ });
343
+
344
+ it('should handle content with only whitespace', () => {
345
+ const content = '\n\n';
346
+ const breakPoints = findSourceCodeBreakPoints(content);
347
+ const blanks = breakPoints.filter(bp => bp.type === 'blank' || bp.type === 'double-blank');
348
+ expect(blanks.length).toBeGreaterThanOrEqual(2);
349
+ });
350
+ });
351
+
352
+ describe('chunkSourceCode', () => {
353
+ it('should return single chunk for short content', () => {
354
+ const content = 'const x = 1;';
355
+ const hash = 'test-hash';
356
+ const chunks = chunkSourceCode(content, hash, '/workspace/file.ts', '/workspace');
357
+
358
+ expect(chunks.length).toBe(1);
359
+ expect(chunks[0].hash).toBe(hash);
360
+ expect(chunks[0].seq).toBe(0);
361
+ expect(chunks[0].pos).toBe(0);
362
+ expect(chunks[0].startLine).toBe(1);
363
+ expect(chunks[0].endLine).toBe(1);
364
+ });
365
+
366
+ it('should include metadata header', () => {
367
+ const content = 'const x = 1;';
368
+ const hash = 'test-hash';
369
+ const chunks = chunkSourceCode(content, hash, '/workspace/src/file.ts', '/workspace');
370
+
371
+ expect(chunks[0].text).toContain('File: src/file.ts');
372
+ expect(chunks[0].text).toContain('Language: typescript');
373
+ expect(chunks[0].text).toContain('Lines: 1-1');
374
+ });
375
+
376
+ it('should use relative path in metadata', () => {
377
+ const content = 'const x = 1;';
378
+ const hash = 'test-hash';
379
+ const chunks = chunkSourceCode(content, hash, '/workspace/deep/nested/file.ts', '/workspace');
380
+
381
+ expect(chunks[0].text).toContain('File: deep/nested/file.ts');
382
+ });
383
+
384
+ it('should detect correct language in metadata', () => {
385
+ const pyContent = 'def foo(): pass';
386
+ const chunks = chunkSourceCode(pyContent, 'hash', '/workspace/script.py', '/workspace');
387
+
388
+ expect(chunks[0].text).toContain('Language: python');
389
+ });
390
+
391
+ it('should handle empty content', () => {
392
+ const content = '';
393
+ const hash = 'test-hash';
394
+ const chunks = chunkSourceCode(content, hash, '/workspace/file.ts', '/workspace');
395
+
396
+ expect(chunks.length).toBe(1);
397
+ expect(chunks[0].text).toContain('File:');
398
+ });
399
+
400
+ it('should handle single line content', () => {
401
+ const content = 'export const x = 1;';
402
+ const hash = 'test-hash';
403
+ const chunks = chunkSourceCode(content, hash, '/workspace/file.ts', '/workspace');
404
+
405
+ expect(chunks.length).toBe(1);
406
+ expect(chunks[0].endLine).toBe(1);
407
+ });
408
+
409
+ it('should handle content with only newlines', () => {
410
+ const content = '\n\n\n\n\n';
411
+ const hash = 'test-hash';
412
+ const chunks = chunkSourceCode(content, hash, '/workspace/file.ts', '/workspace');
413
+
414
+ expect(chunks.length).toBe(1);
415
+ });
416
+
417
+ it('should handle file at workspace root', () => {
418
+ const content = 'const x = 1;';
419
+ const hash = 'test-hash';
420
+ const chunks = chunkSourceCode(content, hash, '/workspace/file.ts', '/workspace');
421
+
422
+ expect(chunks[0].text).toContain('File: file.ts');
423
+ });
424
+
425
+ it('should handle deeply nested file', () => {
426
+ const content = 'const x = 1;';
427
+ const hash = 'test-hash';
428
+ const chunks = chunkSourceCode(content, hash, '/workspace/a/b/c/d/file.ts', '/workspace');
429
+
430
+ expect(chunks[0].text).toContain('File: a/b/c/d/file.ts');
431
+ });
432
+
433
+ it('should handle different file types correctly', () => {
434
+ const testCases = [
435
+ { path: '/workspace/file.py', lang: 'python' },
436
+ { path: '/workspace/file.go', lang: 'go' },
437
+ { path: '/workspace/file.rs', lang: 'rust' },
438
+ { path: '/workspace/file.java', lang: 'java' },
439
+ ];
440
+
441
+ for (const tc of testCases) {
442
+ const chunks = chunkSourceCode('code', 'hash', tc.path, '/workspace');
443
+ expect(chunks[0].text).toContain(`Language: ${tc.lang}`);
444
+ }
445
+ });
446
+ });