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