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,331 @@
1
+ import type { Store, CodebaseConfig, CodebaseIndexResult } from './types.js'
2
+ import { computeHash } from './store.js'
3
+ import { chunkSourceCode } from './chunker.js'
4
+ import { parseSize } from './storage.js'
5
+ import * as fs from 'fs'
6
+ import * as path from 'path'
7
+ import fg from 'fast-glob'
8
+
9
+ const DEFAULT_MAX_FILE_SIZE = 5 * 1024 * 1024
10
+ const DEFAULT_CODEBASE_MAX_SIZE = 2 * 1024 * 1024 * 1024
11
+
12
+ const BUILTIN_EXCLUDE_PATTERNS = [
13
+ '**/node_modules/**',
14
+ '**/.git/**',
15
+ '**/dist/**',
16
+ '**/build/**',
17
+ '**/__pycache__/**',
18
+ '**/vendor/**',
19
+ '**/.next/**',
20
+ '**/.nuxt/**',
21
+ '**/target/**',
22
+ '**/*.min.js',
23
+ '**/*.map',
24
+ '**/*.lock',
25
+ '**/*.sum',
26
+ ]
27
+
28
+ const PROJECT_TYPE_MARKERS: Record<string, string[]> = {
29
+ 'package.json': ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'],
30
+ 'pyproject.toml': ['.py', '.pyi'],
31
+ 'setup.py': ['.py', '.pyi'],
32
+ 'requirements.txt': ['.py', '.pyi'],
33
+ 'go.mod': ['.go'],
34
+ 'Cargo.toml': ['.rs'],
35
+ 'pom.xml': ['.java', '.kt', '.kts'],
36
+ 'build.gradle': ['.java', '.kt', '.kts'],
37
+ 'build.gradle.kts': ['.java', '.kt', '.kts'],
38
+ 'Gemfile': ['.rb', '.erb'],
39
+ }
40
+
41
+ export function detectProjectType(workspaceRoot: string): string[] {
42
+ const extensions = new Set<string>()
43
+
44
+ for (const [marker, exts] of Object.entries(PROJECT_TYPE_MARKERS)) {
45
+ const markerPath = path.join(workspaceRoot, marker)
46
+ if (fs.existsSync(markerPath)) {
47
+ for (const ext of exts) {
48
+ extensions.add(ext)
49
+ }
50
+ }
51
+ }
52
+
53
+ extensions.add('.md')
54
+
55
+ if (extensions.size === 1) {
56
+ return ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java', '.rb', '.md']
57
+ }
58
+
59
+ return Array.from(extensions)
60
+ }
61
+
62
+ export function loadGitignorePatterns(workspaceRoot: string): string[] {
63
+ const gitignorePath = path.join(workspaceRoot, '.gitignore')
64
+
65
+ if (!fs.existsSync(gitignorePath)) {
66
+ return []
67
+ }
68
+
69
+ try {
70
+ const content = fs.readFileSync(gitignorePath, 'utf-8')
71
+ const patterns: string[] = []
72
+
73
+ for (const line of content.split('\n')) {
74
+ const trimmed = line.trim()
75
+ if (trimmed && !trimmed.startsWith('#')) {
76
+ patterns.push(trimmed)
77
+ }
78
+ }
79
+
80
+ return patterns
81
+ } catch {
82
+ return []
83
+ }
84
+ }
85
+
86
+ export function mergeExcludePatterns(config: CodebaseConfig, workspaceRoot: string): string[] {
87
+ const patterns = new Set<string>(BUILTIN_EXCLUDE_PATTERNS)
88
+
89
+ const gitignorePatterns = loadGitignorePatterns(workspaceRoot)
90
+ for (const pattern of gitignorePatterns) {
91
+ patterns.add(pattern)
92
+ }
93
+
94
+ if (config.exclude) {
95
+ for (const pattern of config.exclude) {
96
+ patterns.add(pattern)
97
+ }
98
+ }
99
+
100
+ return Array.from(patterns)
101
+ }
102
+
103
+ export function resolveExtensions(config: CodebaseConfig, workspaceRoot: string): string[] {
104
+ if (config.extensions && config.extensions.length > 0) {
105
+ return config.extensions
106
+ }
107
+
108
+ return detectProjectType(workspaceRoot)
109
+ }
110
+
111
+ export async function scanCodebaseFiles(
112
+ workspaceRoot: string,
113
+ config: CodebaseConfig
114
+ ): Promise<{ files: string[]; skippedTooLarge: number }> {
115
+ const extensions = resolveExtensions(config, workspaceRoot)
116
+ const excludePatterns = mergeExcludePatterns(config, workspaceRoot)
117
+
118
+ const maxFileSize = config.maxFileSize
119
+ ? parseSize(config.maxFileSize)
120
+ : DEFAULT_MAX_FILE_SIZE
121
+
122
+ const effectiveMaxSize = maxFileSize > 0 ? maxFileSize : DEFAULT_MAX_FILE_SIZE
123
+
124
+ const patterns = extensions.map(ext => `**/*${ext}`)
125
+
126
+ const allFiles = await fg(patterns, {
127
+ cwd: workspaceRoot,
128
+ absolute: true,
129
+ onlyFiles: true,
130
+ ignore: excludePatterns,
131
+ })
132
+
133
+ const files: string[] = []
134
+ let skippedTooLarge = 0
135
+
136
+ for (const filePath of allFiles) {
137
+ try {
138
+ const stats = fs.statSync(filePath)
139
+ if (stats.size <= effectiveMaxSize) {
140
+ files.push(filePath)
141
+ } else {
142
+ skippedTooLarge++
143
+ }
144
+ } catch {
145
+ continue
146
+ }
147
+ }
148
+
149
+ return { files, skippedTooLarge }
150
+ }
151
+
152
+ export async function indexCodebase(
153
+ store: Store,
154
+ workspaceRoot: string,
155
+ config: CodebaseConfig,
156
+ projectHash: string,
157
+ _embedder?: { embed(text: string): Promise<{ embedding: number[] }> } | null
158
+ ): Promise<CodebaseIndexResult> {
159
+ const { files, skippedTooLarge } = await scanCodebaseFiles(workspaceRoot, config)
160
+ const maxSizeBytes = config.maxSize
161
+ ? parseSize(config.maxSize)
162
+ : DEFAULT_CODEBASE_MAX_SIZE
163
+ const effectiveMaxSize = maxSizeBytes > 0 ? maxSizeBytes : DEFAULT_CODEBASE_MAX_SIZE
164
+ const batchSize = config.batchSize ?? 50
165
+ let currentStorageUsed = store.getCollectionStorageSize('codebase')
166
+ let filesIndexed = 0
167
+ let filesSkippedUnchanged = 0
168
+ let filesSkippedBudget = 0
169
+ let chunksCreated = 0
170
+ const activePaths: string[] = []
171
+ let batchNum = 0
172
+
173
+ for (let i = 0; i < files.length; i++) {
174
+ const filePath = files[i]
175
+ try {
176
+ const content = fs.readFileSync(filePath, 'utf-8')
177
+ const contentSize = Buffer.byteLength(content, 'utf-8')
178
+ const hash = computeHash(content)
179
+ const existingDoc = store.findDocument(filePath)
180
+ if (existingDoc && existingDoc.hash === hash) {
181
+ filesSkippedUnchanged++
182
+ activePaths.push(filePath)
183
+ continue
184
+ }
185
+ const existingSize = existingDoc ? Buffer.byteLength(store.getDocumentBody(existingDoc.hash) ?? '', 'utf-8') : 0
186
+ const netIncrease = contentSize - existingSize
187
+ if (currentStorageUsed + netIncrease > effectiveMaxSize) {
188
+ filesSkippedBudget++
189
+ if (existingDoc) {
190
+ activePaths.push(filePath)
191
+ }
192
+ continue
193
+ }
194
+ store.insertContent(hash, content)
195
+ const chunks = chunkSourceCode(content, hash, filePath, workspaceRoot)
196
+ chunksCreated += chunks.length
197
+ const title = path.basename(filePath)
198
+ const now = new Date().toISOString()
199
+ store.insertDocument({
200
+ collection: 'codebase',
201
+ path: filePath,
202
+ title,
203
+ hash,
204
+ createdAt: existingDoc?.createdAt ?? now,
205
+ modifiedAt: now,
206
+ active: true,
207
+ projectHash,
208
+ })
209
+ currentStorageUsed += netIncrease
210
+ filesIndexed++
211
+ activePaths.push(filePath)
212
+ } catch {
213
+ continue
214
+ }
215
+
216
+ if ((i + 1) % batchSize === 0) {
217
+ batchNum++
218
+ console.error(`[codebase] Batch ${batchNum}: indexed ${i + 1}/${files.length} files`)
219
+ await new Promise(resolve => setImmediate(resolve))
220
+ }
221
+ }
222
+
223
+ store.bulkDeactivateExcept('codebase', activePaths)
224
+ const finalStorageUsed = store.getCollectionStorageSize('codebase')
225
+ return {
226
+ filesScanned: files.length,
227
+ filesIndexed,
228
+ filesSkippedUnchanged,
229
+ filesSkippedTooLarge: skippedTooLarge,
230
+ filesSkippedBudget,
231
+ chunksCreated,
232
+ storageUsedBytes: finalStorageUsed,
233
+ maxSizeBytes: effectiveMaxSize,
234
+ }
235
+ }
236
+
237
+ const MAX_EMBED_CHARS = 1800
238
+
239
+ function truncateForEmbedding(text: string): string {
240
+ if (text.length <= MAX_EMBED_CHARS) return text
241
+ return text.substring(0, MAX_EMBED_CHARS)
242
+ }
243
+
244
+ export async function embedPendingCodebase(
245
+ store: Store,
246
+ embedder: { embed(text: string): Promise<{ embedding: number[] }>; embedBatch?(texts: string[]): Promise<Array<{ embedding: number[] }>> },
247
+ batchSize: number = 10,
248
+ projectHash?: string
249
+ ): Promise<number> {
250
+ let embedded = 0
251
+ while (true) {
252
+ const batch: Array<{ hash: string; body: string; path: string }> = []
253
+ for (let i = 0; i < batchSize; i++) {
254
+ const row = store.getNextHashNeedingEmbedding(projectHash)
255
+ if (!row) break
256
+ batch.push(row)
257
+ }
258
+ if (batch.length === 0) break
259
+
260
+ const texts = batch.map(row => truncateForEmbedding(row.body))
261
+
262
+ console.error(`[embed] Batch ${batch.length} docs: ${batch.map((b, i) => `${b.path.split('/').pop()}(${texts[i].length}ch)`).join(', ')}`)
263
+ try {
264
+ if (embedder.embedBatch && batch.length > 1) {
265
+ const results = await embedder.embedBatch(texts)
266
+ for (let i = 0; i < batch.length; i++) {
267
+ store.insertEmbedding(batch[i].hash, 0, 0, results[i].embedding, 'nomic-embed-text-v1.5')
268
+ }
269
+ embedded += batch.length
270
+ } else {
271
+ for (let i = 0; i < batch.length; i++) {
272
+ try {
273
+ const result = await embedder.embed(texts[i])
274
+ store.insertEmbedding(batch[i].hash, 0, 0, result.embedding, 'nomic-embed-text-v1.5')
275
+ embedded++
276
+ } catch {
277
+ console.warn(`[embed] Failed to embed ${batch[i].path}, skipping`)
278
+ continue
279
+ }
280
+ }
281
+ }
282
+ } catch (err) {
283
+ console.warn('[embed] Batch failed, falling back to sequential:', err)
284
+ for (const item of batch) {
285
+ try {
286
+ const result = await embedder.embed(truncateForEmbedding(item.body))
287
+ store.insertEmbedding(item.hash, 0, 0, result.embedding, 'nomic-embed-text-v1.5')
288
+ embedded++
289
+ } catch {
290
+ console.warn(`[embed] Skipping ${item.path}`)
291
+ continue
292
+ }
293
+ }
294
+ }
295
+
296
+ if (embedded > 0 && embedded % 50 === 0) {
297
+ console.log(`[embed] Embedded ${embedded} document(s)...`)
298
+ }
299
+
300
+ await new Promise(resolve => setImmediate(resolve))
301
+ }
302
+ return embedded
303
+ }
304
+
305
+ export function getCodebaseStats(
306
+ store: Store,
307
+ config: CodebaseConfig | undefined,
308
+ workspaceRoot: string
309
+ ): { enabled: boolean; documents: number; chunks: number; extensions: string[]; excludeCount: number; storageUsed: number; maxSize: number } | undefined {
310
+ if (!config?.enabled) {
311
+ return undefined
312
+ }
313
+ const health = store.getIndexHealth()
314
+ const codebaseCollection = health.collections.find(c => c.name === 'codebase')
315
+ const extensions = resolveExtensions(config, workspaceRoot)
316
+ const excludePatterns = mergeExcludePatterns(config, workspaceRoot)
317
+ const storageUsed = store.getCollectionStorageSize('codebase')
318
+ const maxSize = config.maxSize
319
+ ? parseSize(config.maxSize)
320
+ : DEFAULT_CODEBASE_MAX_SIZE
321
+ const effectiveMaxSize = maxSize > 0 ? maxSize : DEFAULT_CODEBASE_MAX_SIZE
322
+ return {
323
+ enabled: true,
324
+ documents: codebaseCollection?.documentCount ?? 0,
325
+ chunks: 0,
326
+ extensions,
327
+ excludeCount: excludePatterns.length,
328
+ storageUsed,
329
+ maxSize: effectiveMaxSize,
330
+ }
331
+ }
@@ -0,0 +1,192 @@
1
+ import type { Collection, CollectionConfig } from './types.js';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import { parse, stringify } from 'yaml';
6
+ import fg from 'fast-glob';
7
+
8
+ export function loadCollectionConfig(configPath: string): CollectionConfig | null {
9
+ if (!fs.existsSync(configPath)) {
10
+ return null;
11
+ }
12
+
13
+ const content = fs.readFileSync(configPath, 'utf-8');
14
+ const config = parse(content) as CollectionConfig;
15
+ return config;
16
+ }
17
+
18
+ export function saveCollectionConfig(configPath: string, config: CollectionConfig): void {
19
+ const dir = path.dirname(configPath);
20
+ if (!fs.existsSync(dir)) {
21
+ fs.mkdirSync(dir, { recursive: true });
22
+ }
23
+
24
+ const yaml = stringify(config);
25
+ fs.writeFileSync(configPath, yaml, 'utf-8');
26
+ }
27
+
28
+ export function getCollections(config: CollectionConfig): Collection[] {
29
+ const collections: Collection[] = [];
30
+
31
+ for (const [name, collectionData] of Object.entries(config.collections)) {
32
+ collections.push({
33
+ name,
34
+ path: collectionData.path,
35
+ pattern: collectionData.pattern || '**/*.md',
36
+ context: collectionData.context,
37
+ });
38
+ }
39
+
40
+ return collections;
41
+ }
42
+
43
+ export function addCollection(
44
+ configPath: string,
45
+ name: string,
46
+ collectionPath: string,
47
+ pattern?: string
48
+ ): CollectionConfig {
49
+ let config = loadCollectionConfig(configPath);
50
+
51
+ if (!config) {
52
+ config = {
53
+ collections: {},
54
+ };
55
+ }
56
+
57
+ config.collections[name] = {
58
+ path: collectionPath,
59
+ pattern: pattern || '**/*.md',
60
+ update: 'auto',
61
+ };
62
+
63
+ saveCollectionConfig(configPath, config);
64
+ return config;
65
+ }
66
+
67
+ export function removeCollection(configPath: string, name: string): CollectionConfig {
68
+ const config = loadCollectionConfig(configPath);
69
+
70
+ if (!config) {
71
+ throw new Error('Config file not found');
72
+ }
73
+
74
+ delete config.collections[name];
75
+
76
+ saveCollectionConfig(configPath, config);
77
+ return config;
78
+ }
79
+
80
+ export function listCollections(config: CollectionConfig): string[] {
81
+ return Object.keys(config.collections);
82
+ }
83
+
84
+ export function renameCollection(
85
+ configPath: string,
86
+ oldName: string,
87
+ newName: string
88
+ ): CollectionConfig {
89
+ const config = loadCollectionConfig(configPath);
90
+
91
+ if (!config) {
92
+ throw new Error('Config file not found');
93
+ }
94
+
95
+ if (!config.collections[oldName]) {
96
+ throw new Error(`Collection "${oldName}" not found`);
97
+ }
98
+
99
+ config.collections[newName] = config.collections[oldName];
100
+ delete config.collections[oldName];
101
+
102
+ saveCollectionConfig(configPath, config);
103
+ return config;
104
+ }
105
+
106
+ export function addContext(
107
+ configPath: string,
108
+ collectionName: string,
109
+ pathPrefix: string,
110
+ description: string
111
+ ): CollectionConfig {
112
+ const config = loadCollectionConfig(configPath);
113
+
114
+ if (!config) {
115
+ throw new Error('Config file not found');
116
+ }
117
+
118
+ if (!config.collections[collectionName]) {
119
+ throw new Error(`Collection "${collectionName}" not found`);
120
+ }
121
+
122
+ if (!config.collections[collectionName].context) {
123
+ config.collections[collectionName].context = {};
124
+ }
125
+
126
+ config.collections[collectionName].context![pathPrefix] = description;
127
+
128
+ saveCollectionConfig(configPath, config);
129
+ return config;
130
+ }
131
+
132
+ export function findContextForPath(config: CollectionConfig, filePath: string): string | null {
133
+ let longestMatch: { prefix: string; description: string } | null = null;
134
+
135
+ for (const collectionData of Object.values(config.collections)) {
136
+ if (!collectionData.context) {
137
+ continue;
138
+ }
139
+
140
+ for (const [prefix, description] of Object.entries(collectionData.context)) {
141
+ if (filePath.includes(prefix)) {
142
+ if (!longestMatch || prefix.length > longestMatch.prefix.length) {
143
+ longestMatch = { prefix, description };
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ return longestMatch ? longestMatch.description : null;
150
+ }
151
+
152
+ export function listAllContexts(
153
+ config: CollectionConfig
154
+ ): Array<{ collection: string; prefix: string; description: string }> {
155
+ const contexts: Array<{ collection: string; prefix: string; description: string }> = [];
156
+
157
+ for (const [collectionName, collectionData] of Object.entries(config.collections)) {
158
+ if (!collectionData.context) {
159
+ continue;
160
+ }
161
+
162
+ for (const [prefix, description] of Object.entries(collectionData.context)) {
163
+ contexts.push({
164
+ collection: collectionName,
165
+ prefix,
166
+ description,
167
+ });
168
+ }
169
+ }
170
+
171
+ return contexts;
172
+ }
173
+
174
+ export async function scanCollectionFiles(collection: Collection): Promise<string[]> {
175
+ const expandedPath = collection.path.replace(/^~/, os.homedir());
176
+
177
+ if (!fs.existsSync(expandedPath)) {
178
+ return [];
179
+ }
180
+
181
+ const files = await fg(collection.pattern, {
182
+ cwd: expandedPath,
183
+ absolute: true,
184
+ onlyFiles: true,
185
+ });
186
+
187
+ return files;
188
+ }
189
+
190
+ export function resolveCollectionPath(collection: Collection, basePath: string): string {
191
+ return collection.path;
192
+ }