cntx-ui 2.0.15 → 3.0.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.
Files changed (32) hide show
  1. package/README.md +40 -344
  2. package/bin/cntx-ui-mcp.sh +3 -0
  3. package/bin/cntx-ui.js +2 -1
  4. package/lib/agent-runtime.js +161 -1340
  5. package/lib/agent-tools.js +9 -7
  6. package/lib/api-router.js +262 -79
  7. package/lib/bundle-manager.js +172 -407
  8. package/lib/configuration-manager.js +94 -59
  9. package/lib/database-manager.js +397 -0
  10. package/lib/file-system-manager.js +17 -0
  11. package/lib/heuristics-manager.js +119 -17
  12. package/lib/mcp-server.js +125 -55
  13. package/lib/semantic-splitter.js +222 -481
  14. package/lib/simple-vector-store.js +69 -300
  15. package/package.json +18 -31
  16. package/server.js +151 -73
  17. package/templates/TOOLS.md +41 -0
  18. package/templates/activities/activities/create-project-bundles/README.md +4 -3
  19. package/templates/activities/activities/create-project-bundles/notes.md +15 -19
  20. package/templates/activities/activities/create-project-bundles/tasks.md +4 -4
  21. package/templates/activities/activities.json +1 -1
  22. package/templates/agent-config.yaml +0 -13
  23. package/templates/agent-instructions.md +22 -6
  24. package/templates/agent-rules/capabilities/bundle-system.md +1 -1
  25. package/templates/agent-rules/project-specific/architecture.md +1 -1
  26. package/web/dist/assets/index-B2OdTzzI.css +1 -0
  27. package/web/dist/assets/index-D0tBsKiR.js +2016 -0
  28. package/web/dist/index.html +2 -2
  29. package/mcp-config-example.json +0 -9
  30. package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +0 -1
  31. package/web/dist/assets/index-dF3qg-y_.js +0 -2486
  32. package/web/dist/assets/index-h5FGSg_P.css +0 -1
@@ -1,588 +1,329 @@
1
1
  /**
2
- * True Semantic Splitting - Function-level code chunks with context
3
- * Creates surgical, self-contained chunks for AI consumption
4
- * Operates parallel to file-level bundle system
2
+ * Semantic Splitter - High-Performance AST-based Chunker
3
+ * Uses tree-sitter for surgical, function-level code extraction
4
+ * Integrated with HeuristicsManager for intelligent categorization
5
5
  */
6
6
 
7
7
  import { readFileSync, existsSync } from 'fs'
8
- import { extname, basename, dirname, join } from 'path'
8
+ import { join, extname } from 'path'
9
9
  import glob from 'glob'
10
+ import Parser from 'tree-sitter'
11
+ import JavaScript from 'tree-sitter-javascript'
12
+ import TypeScript from 'tree-sitter-typescript'
10
13
  import HeuristicsManager from './heuristics-manager.js'
11
14
 
12
15
  export default class SemanticSplitter {
13
16
  constructor(options = {}) {
14
17
  this.options = {
15
- maxChunkSize: 2000, // Max chars per chunk
18
+ maxChunkSize: 3000, // Max chars per chunk
16
19
  includeContext: true, // Include imports/types needed
17
- groupRelated: true, // Group related functions
18
- minFunctionSize: 50, // Skip tiny functions
20
+ minFunctionSize: 40, // Skip tiny functions
19
21
  ...options
20
22
  }
21
23
 
22
- // Initialize heuristics manager
24
+ // Initialize tree-sitter parsers
25
+ this.parsers = {
26
+ javascript: new Parser(),
27
+ typescript: new Parser(),
28
+ tsx: new Parser()
29
+ }
30
+ this.parsers.javascript.setLanguage(JavaScript)
31
+ this.parsers.typescript.setLanguage(TypeScript.typescript)
32
+ this.parsers.tsx.setLanguage(TypeScript.tsx)
33
+
23
34
  this.heuristicsManager = new HeuristicsManager()
24
35
  }
25
36
 
37
+ getParser(filePath) {
38
+ const ext = extname(filePath)
39
+ switch (ext) {
40
+ case '.ts': return this.parsers.typescript
41
+ case '.tsx': return this.parsers.tsx
42
+ default: return this.parsers.javascript
43
+ }
44
+ }
45
+
26
46
  /**
27
- * Extract semantic chunks from project
47
+ * Main entry point - extract semantic chunks from project
28
48
  */
29
49
  async extractSemanticChunks(projectPath, patterns = ['**/*.{js,jsx,ts,tsx,mjs}'], bundleConfig = null) {
30
- console.log('🔪 Starting semantic splitting...')
31
-
32
- const files = this.findFiles(projectPath, patterns)
33
- console.log(`📁 Found ${files.length} files to split`)
34
-
35
- // Load bundle configuration if provided
36
- this.bundleConfig = bundleConfig
50
+ console.log('🔪 Starting surgical semantic splitting via tree-sitter...');
51
+ console.log(`📂 Project path: ${projectPath}`);
52
+ console.log(`📋 Patterns: ${patterns}`);
53
+
54
+ this.bundleConfig = bundleConfig;
55
+ const files = this.findFiles(projectPath, patterns);
56
+ console.log(`📁 Found ${files.length} files to split`);
57
+ if (files.length > 0) {
58
+ console.log('📄 Sample files:', files.slice(0, 5));
59
+ }
37
60
 
38
- const allFunctions = []
39
- const allTypes = []
40
- const allImports = []
61
+ const allChunks = []
41
62
 
42
- // Extract all code elements
43
63
  for (const filePath of files) {
44
64
  try {
45
- const elements = this.extractCodeElements(filePath, projectPath)
46
- allFunctions.push(...elements.functions)
47
- allTypes.push(...elements.types)
48
- allImports.push(...elements.imports)
65
+ const fileChunks = this.processFile(filePath, projectPath)
66
+ allChunks.push(...fileChunks)
49
67
  } catch (error) {
50
- console.warn(`Failed to extract from ${filePath}: ${error.message}`)
68
+ console.warn(`Failed to process ${filePath}: ${error.message}`)
51
69
  }
52
70
  }
53
71
 
54
- console.log(`⚡ Extracted ${allFunctions.length} functions, ${allTypes.length} types`)
55
-
56
- // Create semantic chunks
57
- const chunks = this.createSemanticChunks(allFunctions, allTypes, allImports)
58
- console.log(`🧩 Created ${chunks.length} semantic chunks`)
59
-
72
+ console.log(`🧩 Created ${allChunks.length} semantic chunks across project`)
60
73
  return {
61
74
  summary: {
62
75
  totalFiles: files.length,
63
- totalFunctions: allFunctions.length,
64
- totalChunks: chunks.length,
65
- averageChunkSize: chunks.reduce((sum, c) => sum + c.code.length, 0) / chunks.length
76
+ totalChunks: allChunks.length,
77
+ averageSize: allChunks.reduce((sum, c) => sum + c.code.length, 0) / allChunks.length
66
78
  },
67
- chunks: chunks
79
+ chunks: allChunks
68
80
  }
69
81
  }
70
82
 
71
- /**
72
- * Find files to analyze (same logic as bundles)
73
- */
74
83
  findFiles(projectPath, patterns) {
75
84
  const files = []
76
-
77
85
  for (const pattern of patterns) {
78
86
  const matches = glob.sync(pattern, {
79
87
  cwd: projectPath,
80
- ignore: [
81
- 'node_modules/**', 'dist/**', 'build/**', '.git/**',
82
- '*.test.*', '*.spec.*', '**/test/**', '**/tests/**',
83
- '**/*.min.js', '**/*.bundle.js'
84
- ]
88
+ ignore: ['node_modules/**', 'dist/**', '.git/**', '*.test.*', '*.spec.*']
85
89
  })
86
-
87
- files.push(...matches.filter(file =>
88
- !file.includes('node_modules') &&
89
- !file.includes('dist/') &&
90
- !file.includes('.min.')
91
- ))
90
+ files.push(...matches)
92
91
  }
93
-
94
92
  return [...new Set(files)]
95
93
  }
96
94
 
97
- /**
98
- * Extract functions, types, and imports from a file
99
- */
100
- extractCodeElements(relativePath, projectPath) {
101
- const fullPath = join(projectPath, relativePath)
102
- if (!existsSync(fullPath)) return { functions: [], types: [], imports: [] }
103
-
104
- const content = readFileSync(fullPath, 'utf8')
105
- const lines = content.split('\n')
106
-
107
- return {
108
- functions: this.extractFunctions(content, lines, relativePath),
109
- types: this.extractTypes(content, lines, relativePath),
110
- imports: this.extractImports(content, relativePath)
111
- }
112
- }
95
+ processFile(relativePath, projectPath) {
96
+ const fullPath = join(projectPath, relativePath);
97
+ if (!existsSync(fullPath)) return [];
98
+
99
+ // console.log(` 📄 Processing: ${relativePath}`);
100
+ const content = readFileSync(fullPath, 'utf8');
101
+ const parser = this.getParser(relativePath);
102
+ const tree = parser.parse(content);
103
+ const root = tree.rootNode;
104
+
105
+ const elements = {
106
+ functions: [],
107
+ types: [],
108
+ imports: this.extractImports(root, content, relativePath)
109
+ };
113
110
 
114
- /**
115
- * Extract functions with robust regex patterns
116
- */
117
- extractFunctions(content, lines, filePath) {
118
- const functions = []
119
-
120
- // Pattern 1: Regular function declarations
121
- const functionRegex = /^(\s*)(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/gm
122
-
123
- // Pattern 2: Arrow functions assigned to const/let
124
- const arrowRegex = /^(\s*)(?:export\s+)?const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>\s*[\{]/gm
125
-
126
- // Pattern 3: Class methods
127
- const methodRegex = /^(\s+)(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/gm
128
-
129
- // Pattern 4: React components (function components)
130
- const componentRegex = /^(\s*)(?:export\s+(?:default\s+)?)?function\s+([A-Z][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/gm
131
-
132
- const patterns = [
133
- { regex: functionRegex, type: 'function' },
134
- { regex: arrowRegex, type: 'arrow_function' },
135
- { regex: methodRegex, type: 'method' },
136
- { regex: componentRegex, type: 'react_component' }
137
- ]
138
-
139
- for (const { regex, type } of patterns) {
140
- let match
141
- while ((match = regex.exec(content)) !== null) {
142
- const functionName = match[2]
143
- const indentation = match[1]
144
- const startIndex = match.index
145
-
146
- // Skip if it's a keyword or common false positive
147
- if (['if', 'for', 'while', 'switch', 'catch'].includes(functionName)) {
148
- continue
149
- }
150
-
151
- const startLine = content.substring(0, startIndex).split('\n').length
152
- const functionBody = this.extractFunctionBody(content, startIndex)
153
-
154
- if (functionBody && functionBody.length > this.options.minFunctionSize) {
155
- functions.push({
156
- name: functionName,
157
- type: type,
158
- filePath: filePath,
159
- startLine: startLine,
160
- code: functionBody,
161
- indentation: indentation.length,
162
- isExported: match[0].includes('export'),
163
- isAsync: match[0].includes('async'),
164
- size: functionBody.length
165
- })
166
- }
167
- }
168
- }
169
-
170
- return functions
171
- }
111
+ // Traverse AST for functions and types
112
+ this.traverse(root, content, relativePath, elements);
172
113
 
173
- /**
174
- * Extract function body using brace matching
175
- */
176
- extractFunctionBody(content, startIndex) {
177
- const openBraceIndex = content.indexOf('{', startIndex)
178
- if (openBraceIndex === -1) return null
179
-
180
- let braceCount = 0
181
- let currentIndex = openBraceIndex
182
- let inString = false
183
- let stringChar = null
184
-
185
- while (currentIndex < content.length) {
186
- const char = content[currentIndex]
187
- const prevChar = content[currentIndex - 1] || ''
188
-
189
- // Handle string literals
190
- if ((char === '"' || char === "'" || char === '`') && prevChar !== '\\') {
191
- if (!inString) {
192
- inString = true
193
- stringChar = char
194
- } else if (char === stringChar) {
195
- inString = false
196
- stringChar = null
197
- }
198
- }
199
-
200
- // Count braces outside strings
201
- if (!inString) {
202
- if (char === '{') braceCount++
203
- else if (char === '}') braceCount--
204
-
205
- if (braceCount === 0) {
206
- // Found the closing brace
207
- return content.substring(startIndex, currentIndex + 1).trim()
208
- }
209
- }
210
-
211
- currentIndex++
212
- }
213
-
214
- return null // Unmatched braces
114
+ const chunks = this.createChunks(elements, content, relativePath);
115
+ // if (chunks.length > 0) {
116
+ // console.log(` ✅ Found ${chunks.length} chunks in ${relativePath}`);
117
+ // }
118
+ return chunks;
215
119
  }
216
120
 
217
- /**
218
- * Extract type definitions and interfaces
219
- */
220
- extractTypes(content, lines, filePath) {
221
- const types = []
222
-
223
- // TypeScript interfaces
224
- const interfaceRegex = /^(\s*)(?:export\s+)?interface\s+([A-Z][a-zA-Z0-9_$]*)\s*\{/gm
225
-
226
- // Type aliases
227
- const typeRegex = /^(\s*)(?:export\s+)?type\s+([A-Z][a-zA-Z0-9_$]*)\s*=/gm
228
-
229
- const patterns = [
230
- { regex: interfaceRegex, type: 'interface' },
231
- { regex: typeRegex, type: 'type_alias' }
232
- ]
233
-
234
- for (const { regex, type } of patterns) {
235
- let match
236
- while ((match = regex.exec(content)) !== null) {
237
- const typeName = match[2]
238
- const startIndex = match.index
239
- const startLine = content.substring(0, startIndex).split('\n').length
240
-
241
- let typeBody
242
- if (type === 'interface') {
243
- typeBody = this.extractTypeBody(content, startIndex)
244
- } else {
245
- // For type aliases, extract until semicolon or newline
246
- const endIndex = content.indexOf(';', startIndex)
247
- typeBody = content.substring(startIndex, endIndex + 1).trim()
248
- }
249
-
250
- if (typeBody) {
251
- types.push({
252
- name: typeName,
253
- type: type,
254
- filePath: filePath,
255
- startLine: startLine,
256
- code: typeBody,
257
- isExported: match[0].includes('export')
258
- })
259
- }
121
+ traverse(node, content, filePath, elements) {
122
+ // Detect Function Declarations
123
+ if (node.type === 'function_declaration' || node.type === 'method_definition' || node.type === 'arrow_function') {
124
+ const func = this.mapFunctionNode(node, content, filePath)
125
+ if (func && func.code.length > this.options.minFunctionSize) {
126
+ elements.functions.push(func)
260
127
  }
261
128
  }
262
-
263
- return types
264
- }
265
129
 
266
- /**
267
- * Extract type body (similar to function body)
268
- */
269
- extractTypeBody(content, startIndex) {
270
- const openBraceIndex = content.indexOf('{', startIndex)
271
- if (openBraceIndex === -1) return null
272
-
273
- let braceCount = 0
274
- let currentIndex = openBraceIndex
275
-
276
- while (currentIndex < content.length) {
277
- const char = content[currentIndex]
278
-
279
- if (char === '{') braceCount++
280
- else if (char === '}') braceCount--
281
-
282
- if (braceCount === 0) {
283
- return content.substring(startIndex, currentIndex + 1).trim()
284
- }
285
-
286
- currentIndex++
130
+ // Detect Type Definitions (TS)
131
+ if (node.type === 'interface_declaration' || node.type === 'type_alias_declaration') {
132
+ const typeDef = this.mapTypeNode(node, content, filePath)
133
+ if (typeDef) elements.types.push(typeDef)
287
134
  }
288
-
289
- return null
290
- }
291
135
 
292
- /**
293
- * Extract import statements
294
- */
295
- extractImports(content, filePath) {
296
- const imports = []
297
- const importRegex = /^(\s*)import\s+(.+?)\s+from\s+['"`]([^'"`]+)['"`]/gm
298
-
299
- let match
300
- while ((match = importRegex.exec(content)) !== null) {
301
- const importStatement = match[0].trim()
302
- const importPath = match[3]
303
-
304
- imports.push({
305
- statement: importStatement,
306
- path: importPath,
307
- filePath: filePath,
308
- isRelative: importPath.startsWith('.'),
309
- isExternal: !importPath.startsWith('.')
310
- })
311
- }
312
-
313
- return imports
314
- }
315
-
316
- /**
317
- * Create semantic chunks from extracted elements
318
- */
319
- createSemanticChunks(functions, types, imports) {
320
- const chunks = []
321
-
322
- // Create function-level chunks
323
- for (const func of functions) {
324
- const chunk = this.createFunctionChunk(func, types, imports)
325
- if (chunk) {
326
- chunks.push(chunk)
136
+ // Recurse unless we've already captured the block (like a function body)
137
+ if (node.type !== 'function_declaration' && node.type !== 'method_definition') {
138
+ for (let i = 0; i < node.namedChildCount; i++) {
139
+ this.traverse(node.namedChild(i), content, filePath, elements)
327
140
  }
328
141
  }
329
-
330
- // Create type-only chunks for standalone types
331
- for (const type of types) {
332
- if (!this.isTypeUsedInFunctions(type, functions)) {
333
- chunks.push(this.createTypeChunk(type, imports))
334
- }
335
- }
336
-
337
- return chunks
338
142
  }
339
143
 
340
- /**
341
- * Create a semantic chunk for a function with its context
342
- */
343
- createFunctionChunk(func, allTypes, allImports) {
344
- let chunkCode = ''
345
- const includedImports = new Set()
346
- const includedTypes = new Set()
347
-
348
- // Find relevant imports for this function
349
- const fileImports = allImports.filter(imp => imp.filePath === func.filePath)
350
-
351
- // Find types referenced in the function
352
- const referencedTypes = this.findReferencedTypes(func.code, allTypes)
353
-
354
- // Add necessary imports
355
- for (const imp of fileImports) {
356
- if (this.isImportRelevant(imp, func.code)) {
357
- chunkCode += imp.statement + '\n'
358
- includedImports.add(imp.path)
144
+ mapFunctionNode(node, content, filePath) {
145
+ let name = 'anonymous';
146
+
147
+ // Find name identifier based on node type
148
+ if (node.type === 'function_declaration' || node.type === 'method_definition') {
149
+ const nameNode = node.childForFieldName('name');
150
+ if (nameNode) name = content.slice(nameNode.startIndex, nameNode.endIndex);
151
+ } else if (node.type === 'arrow_function') {
152
+ // 1. Check if assigned to a variable: const foo = () => {}
153
+ const parent = node.parent;
154
+ if (parent && parent.type === 'variable_declarator') {
155
+ const nameNode = parent.childForFieldName('name');
156
+ if (nameNode) name = content.slice(nameNode.startIndex, nameNode.endIndex);
359
157
  }
360
- }
361
-
362
- // Add referenced types
363
- for (const type of referencedTypes) {
364
- chunkCode += '\n' + type.code + '\n'
365
- includedTypes.add(type.name)
366
- }
367
-
368
- // Add the function itself
369
- chunkCode += '\n' + func.code
370
-
371
- // Create chunk with adaptive sizing - never lose functions
372
- let finalCode = chunkCode.trim()
373
- let contextLevel = 'full'
374
-
375
- // If too large, try with reduced context
376
- if (chunkCode.length > this.options.maxChunkSize) {
377
- // Fallback 1: Function + essential imports only (no types)
378
- finalCode = ''
379
- for (const imp of fileImports.slice(0, 3)) { // Limit to 3 imports
380
- if (this.isImportRelevant(imp, func.code)) {
381
- finalCode += imp.statement + '\n'
382
- }
158
+ // 2. Check if part of an object property: { foo: () => {} }
159
+ else if (parent && parent.type === 'pair') {
160
+ const keyNode = parent.childForFieldName('key');
161
+ if (keyNode) name = content.slice(keyNode.startIndex, keyNode.endIndex);
162
+ }
163
+ // 3. Check if part of an assignment: this.foo = () => {}
164
+ else if (parent && parent.type === 'assignment_expression') {
165
+ const leftNode = parent.childForFieldName('left');
166
+ if (leftNode) name = content.slice(leftNode.startIndex, leftNode.endIndex);
383
167
  }
384
- finalCode += '\n' + func.code
385
- contextLevel = 'reduced'
386
- }
387
-
388
- // If still too large, function only
389
- if (finalCode.length > this.options.maxChunkSize) {
390
- finalCode = func.code
391
- contextLevel = 'minimal'
392
168
  }
169
+
170
+ const code = content.slice(node.startIndex, node.endIndex)
393
171
 
394
- // Always create a chunk - never lose functions
395
172
  return {
396
- name: func.name,
397
- type: 'function_chunk',
398
- subtype: func.type,
399
- code: finalCode,
400
- size: finalCode.length,
401
- filePath: func.filePath,
402
- startLine: func.startLine,
403
- isExported: func.isExported,
404
- isAsync: func.isAsync,
405
- complexity: this.calculateComplexity(func.code),
406
- includes: {
407
- imports: contextLevel === 'minimal' ? [] : Array.from(includedImports),
408
- types: contextLevel === 'full' ? Array.from(includedTypes) : []
409
- },
410
- purpose: this.determinePurpose(func),
411
- tags: [...this.generateTags(func), contextLevel === 'full' ? 'full-context' : contextLevel === 'reduced' ? 'reduced-context' : 'minimal-context'],
412
- bundles: this.getFileBundles(func.filePath)
173
+ name,
174
+ type: node.type,
175
+ filePath,
176
+ startLine: node.startPosition.row + 1,
177
+ code,
178
+ isExported: this.isExported(node),
179
+ isAsync: code.includes('async')
413
180
  }
414
181
  }
415
182
 
416
- /**
417
- * Create a chunk for standalone types
418
- */
419
- createTypeChunk(type, allImports) {
420
- let chunkCode = ''
421
- const includedImports = new Set()
422
-
423
- // Add relevant imports if any
424
- const fileImports = allImports.filter(imp => imp.filePath === type.filePath)
425
- for (const imp of fileImports.slice(0, 3)) { // Limit imports
426
- chunkCode += imp.statement + '\n'
427
- }
428
-
429
- chunkCode += '\n' + type.code
183
+ mapTypeNode(node, content, filePath) {
184
+ const nameNode = node.childForFieldName('name')
185
+ if (!nameNode) return null
430
186
 
431
187
  return {
432
- name: type.name,
433
- type: 'type_chunk',
434
- subtype: type.type,
435
- code: chunkCode.trim(),
436
- size: chunkCode.length,
437
- filePath: type.filePath,
438
- startLine: type.startLine,
439
- isExported: type.isExported,
440
- purpose: 'Type definition',
441
- tags: ['type', type.type],
442
- bundles: this.getFileBundles(type.filePath)
188
+ name: content.slice(nameNode.startIndex, nameNode.endIndex),
189
+ type: node.type,
190
+ filePath,
191
+ startLine: node.startPosition.row + 1,
192
+ code: content.slice(node.startIndex, node.endIndex),
193
+ isExported: this.isExported(node)
443
194
  }
444
195
  }
445
196
 
446
- /**
447
- * Find types referenced in function code
448
- */
449
- findReferencedTypes(functionCode, allTypes) {
450
- const referenced = []
451
-
452
- for (const type of allTypes) {
453
- // Check if type name appears in function code
454
- const typeRegex = new RegExp(`\\b${type.name}\\b`, 'g')
455
- if (typeRegex.test(functionCode)) {
456
- referenced.push(type)
197
+ extractImports(root, content, filePath) {
198
+ const imports = []
199
+ // Simple traversal for import statements
200
+ for (let i = 0; i < root.namedChildCount; i++) {
201
+ const node = root.namedChild(i)
202
+ if (node.type === 'import_statement') {
203
+ imports.push({
204
+ statement: content.slice(node.startIndex, node.endIndex),
205
+ filePath
206
+ })
457
207
  }
458
208
  }
459
-
460
- return referenced
209
+ return imports
461
210
  }
462
211
 
463
- /**
464
- * Check if import is relevant to function
465
- */
466
- isImportRelevant(importStatement, functionCode) {
467
- // Simple heuristic: check if any imported identifiers appear in function
468
- const importMatch = importStatement.statement.match(/import\s+(.+?)\s+from/)
469
- if (!importMatch) return false
470
-
471
- const imported = importMatch[1]
472
-
473
- // Handle different import styles
474
- if (imported.includes('{')) {
475
- // Named imports: import { foo, bar } from 'module'
476
- const namedImports = imported.match(/\{([^}]+)\}/)?.[1]
477
- if (namedImports) {
478
- const names = namedImports.split(',').map(name => name.trim())
479
- return names.some(name => functionCode.includes(name))
480
- }
481
- } else {
482
- // Default import: import foo from 'module'
483
- const defaultImport = imported.trim()
484
- return functionCode.includes(defaultImport)
212
+ isExported(node) {
213
+ let current = node
214
+ while (current) {
215
+ if (current.type === 'export_statement') return true
216
+ current = current.parent
485
217
  }
486
-
487
218
  return false
488
219
  }
489
220
 
490
- /**
491
- * Check if type is used in any function
492
- */
493
- isTypeUsedInFunctions(type, functions) {
494
- const typeRegex = new RegExp(`\\b${type.name}\\b`, 'g')
495
- return functions.some(func => typeRegex.test(func.code))
496
- }
221
+ createChunks(elements, content, filePath) {
222
+ const chunks = [];
223
+ const pathParts = filePath.toLowerCase().split(/[\\\/]/);
224
+
225
+ for (const func of elements.functions) {
226
+ // Pass full context to heuristics
227
+ const heuristicContext = {
228
+ ...func,
229
+ includes: elements,
230
+ pathParts
231
+ };
232
+
233
+ const purpose = this.heuristicsManager.determinePurpose(heuristicContext);
234
+ const businessDomain = this.heuristicsManager.inferBusinessDomains(heuristicContext);
235
+ const technicalPatterns = this.heuristicsManager.inferTechnicalPatterns(heuristicContext);
236
+ const tags = this.generateTags(func);
497
237
 
498
- /**
499
- * Calculate function complexity (cyclomatic complexity)
500
- */
501
- calculateComplexity(code) {
502
- let complexity = 1 // Base complexity
503
-
504
- // Simple complexity indicators - just count control flow structures
505
- const indicators = {
506
- 'if': (code.match(/\bif\s*\(/g) || []).length,
507
- 'else if': (code.match(/\belse\s+if\b/g) || []).length,
508
- 'for': (code.match(/\bfor\s*\(/g) || []).length,
509
- 'while': (code.match(/\bwhile\s*\(/g) || []).length,
510
- 'switch': (code.match(/\bswitch\s*\(/g) || []).length,
511
- 'case': (code.match(/\bcase\s+/g) || []).length,
512
- 'catch': (code.match(/\bcatch\s*\(/g) || []).length,
513
- 'ternary': (code.match(/\?\s*[^?\.\s]/g) || []).length,
514
- 'logical_and': (code.match(/&&\s*[^&=]/g) || []).length,
515
- 'logical_or': (code.match(/\|\|\s*[^|=]/g) || []).length
516
- }
517
-
518
- // Sum all complexity indicators
519
- for (const count of Object.values(indicators)) {
520
- complexity += count
238
+ // if (businessDomain.length > 0) {
239
+ // console.log(` 💎 ${func.name}: Domains: [${businessDomain}], Patterns: [${technicalPatterns}]`);
240
+ // }
241
+
242
+ let chunkCode = '';
243
+ if (this.options.includeContext) {
244
+ const relevantImports = elements.imports
245
+ .filter(imp => this.isImportRelevant(imp.statement, func.code))
246
+ .map(imp => imp.statement)
247
+ .join('\n');
248
+
249
+ if (relevantImports) chunkCode += relevantImports + '\n\n';
250
+ }
251
+ chunkCode += func.code;
252
+
253
+ chunks.push({
254
+ id: `${filePath}:${func.name}:${func.startLine}`,
255
+ name: func.name,
256
+ filePath,
257
+ type: 'function',
258
+ subtype: func.type,
259
+ code: chunkCode,
260
+ startLine: func.startLine,
261
+ complexity: this.calculateComplexity(func.code),
262
+ purpose,
263
+ tags,
264
+ businessDomain,
265
+ technicalPatterns,
266
+ includes: {
267
+ imports: elements.imports.map(i => i.statement),
268
+ types: elements.types.map(t => t.name)
269
+ },
270
+ bundles: this.getFileBundles(filePath)
271
+ });
521
272
  }
522
273
 
523
- // Return complexity with reasonable thresholds
524
- return {
525
- score: complexity,
526
- level: complexity <= 3 ? 'low' : complexity <= 8 ? 'medium' : 'high'
527
- }
274
+ return chunks;
528
275
  }
529
276
 
530
- /**
531
- * Determine function purpose using heuristics configuration
532
- */
533
- determinePurpose(func) {
534
- return this.heuristicsManager.determinePurpose(func)
277
+ isImportRelevant(importStatement, functionCode) {
278
+ // Heuristic: does the function use any name from the import?
279
+ const match = importStatement.match(/import\s+(?:\{([^}]+)\}|(\w+))/i)
280
+ if (!match) return false
281
+ const importedNames = match[1] ? match[1].split(',').map(n => n.trim()) : [match[2]]
282
+ return importedNames.some(name => functionCode.includes(name))
283
+ }
284
+
285
+ calculateComplexity(code) {
286
+ const indicators = ['if', 'else', 'for', 'while', 'switch', 'case', 'catch', '?', '&&', '||'];
287
+ let score = 1;
288
+ indicators.forEach(ind => {
289
+ // Escape special regex characters
290
+ const escaped = ind.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
291
+ // Only use word boundaries for word-like indicators
292
+ const pattern = /^[a-zA-Z]+$/.test(ind) ? `\\b${escaped}\\b` : escaped;
293
+ const regex = new RegExp(pattern, 'g');
294
+ score += (code.match(regex) || []).length;
295
+ });
296
+ return {
297
+ score,
298
+ level: score < 5 ? 'low' : score < 15 ? 'medium' : 'high'
299
+ };
535
300
  }
536
301
 
537
- /**
538
- * Generate tags for function
539
- */
540
302
  generateTags(func) {
541
303
  const tags = [func.type]
542
-
543
304
  if (func.isExported) tags.push('exported')
544
305
  if (func.isAsync) tags.push('async')
545
- if (func.size > 1000) tags.push('large')
546
- if (func.code.includes('console.log')) tags.push('has-logging')
547
- if (func.code.includes('throw')) tags.push('can-throw')
548
- if (func.code.includes('return')) tags.push('returns-value')
549
-
306
+ if (func.code.length > 2000) tags.push('large')
550
307
  return tags
551
308
  }
552
309
 
553
- /**
554
- * Determine which bundles a file belongs to
555
- */
556
310
  getFileBundles(filePath) {
557
311
  if (!this.bundleConfig?.bundles) return []
558
-
559
312
  const bundles = []
560
- for (const [bundleName, patterns] of Object.entries(this.bundleConfig.bundles)) {
561
- // Skip master bundle as requested
562
- if (bundleName === 'master') continue
563
-
564
- // Check if file matches any pattern in this bundle
313
+ for (const [name, patterns] of Object.entries(this.bundleConfig.bundles)) {
314
+ if (name === 'master') continue
565
315
  for (const pattern of patterns) {
566
316
  if (this.matchesPattern(filePath, pattern)) {
567
- bundles.push(bundleName)
568
- break // Don't add the same bundle multiple times
317
+ bundles.push(name)
318
+ break
569
319
  }
570
320
  }
571
321
  }
572
-
573
322
  return bundles
574
323
  }
575
324
 
576
- /**
577
- * Simple pattern matching (basic glob support)
578
- */
579
325
  matchesPattern(filePath, pattern) {
580
- // Convert glob pattern to regex
581
- const regex = pattern
582
- .replace(/\*\*/g, '.*') // ** matches any directories
583
- .replace(/\*/g, '[^/]*') // * matches any characters except /
584
- .replace(/\./g, '\\.') // Escape dots
585
-
326
+ const regex = pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*').replace(/\./g, '\\.')
586
327
  return new RegExp(`^${regex}$`).test(filePath)
587
328
  }
588
- }
329
+ }