cntx-ui 2.0.12 → 2.0.13

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.
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Function-Level Semantic Chunker
3
+ * Extracts individual functions/methods/components as discrete chunks
4
+ * with intelligent context inclusion
5
+ */
6
+
7
+ import { readFileSync, existsSync } from 'fs'
8
+ import { extname, basename, dirname, join } from 'path'
9
+ import glob from 'glob'
10
+
11
+ export default class FunctionLevelChunker {
12
+ constructor(options = {}) {
13
+ this.options = {
14
+ includeContext: true,
15
+ maxContextLines: 50,
16
+ groupRelated: true,
17
+ ...options
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Extract function-level chunks from project
23
+ */
24
+ async extractFunctionChunks(projectPath, patterns = ['**/*.{js,jsx,ts,tsx,mjs}']) {
25
+ console.log('🔍 Starting function-level extraction...')
26
+
27
+ const files = this.findFiles(projectPath, patterns)
28
+ console.log(`📁 Found ${files.length} files to analyze`)
29
+
30
+ const allFunctions = []
31
+ let processedFiles = 0
32
+
33
+ for (const filePath of files) {
34
+ try {
35
+ const functions = this.extractFunctionsFromFile(filePath, projectPath)
36
+ allFunctions.push(...functions)
37
+ processedFiles++
38
+ } catch (error) {
39
+ console.warn(`Failed to extract from ${filePath}: ${error.message}`)
40
+ }
41
+ }
42
+
43
+ console.log(`✅ Extracted ${allFunctions.length} functions from ${processedFiles} files`)
44
+
45
+ // Create semantic chunks from functions
46
+ const chunks = this.createFunctionChunks(allFunctions)
47
+ console.log(`📦 Created ${chunks.length} function-level chunks`)
48
+
49
+ return {
50
+ summary: {
51
+ totalFiles: processedFiles,
52
+ totalFunctions: allFunctions.length,
53
+ totalChunks: chunks.length
54
+ },
55
+ functions: allFunctions,
56
+ chunks: chunks
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Find files to analyze
62
+ */
63
+ findFiles(projectPath, patterns) {
64
+ const files = []
65
+
66
+ for (const pattern of patterns) {
67
+ const matches = glob.sync(pattern, {
68
+ cwd: projectPath,
69
+ ignore: [
70
+ 'node_modules/**', 'dist/**', 'build/**', '.git/**',
71
+ '*.test.*', '*.spec.*', '**/test/**', '**/tests/**',
72
+ '**/*.min.js', '**/*.bundle.js'
73
+ ]
74
+ })
75
+
76
+ files.push(...matches.filter(file =>
77
+ !file.includes('node_modules') &&
78
+ !file.includes('dist/') &&
79
+ !file.includes('.min.')
80
+ ))
81
+ }
82
+
83
+ return [...new Set(files)]
84
+ }
85
+
86
+ /**
87
+ * Extract all functions from a single file
88
+ */
89
+ extractFunctionsFromFile(relativePath, projectPath) {
90
+ const fullPath = join(projectPath, relativePath)
91
+ if (!existsSync(fullPath)) return []
92
+
93
+ const content = readFileSync(fullPath, 'utf8')
94
+ const lines = content.split('\n')
95
+
96
+ const functions = []
97
+
98
+ // Try tree-sitter first, fallback to regex
99
+ try {
100
+ const treeSitterFunctions = this.extractWithTreeSitter(content, relativePath)
101
+ functions.push(...treeSitterFunctions)
102
+ } catch (error) {
103
+ // Fallback to regex extraction
104
+ const regexFunctions = this.extractWithRegex(content, lines, relativePath)
105
+ functions.push(...regexFunctions)
106
+ }
107
+
108
+ return functions
109
+ }
110
+
111
+ /**
112
+ * Extract functions using regex patterns (robust fallback)
113
+ */
114
+ extractWithRegex(content, lines, filePath) {
115
+ const functions = []
116
+
117
+ // Patterns for different function types
118
+ const patterns = [
119
+ // Function declarations: function name() {}
120
+ {
121
+ pattern: /^[\s]*(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/gm,
122
+ type: 'function'
123
+ },
124
+ // Arrow functions: const name = () => {}
125
+ {
126
+ pattern: /^[\s]*(?:export\s+)?const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/gm,
127
+ type: 'arrow_function'
128
+ },
129
+ // Class methods: methodName() {}
130
+ {
131
+ pattern: /^[\s]*(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/gm,
132
+ type: 'method'
133
+ },
134
+ // React components: export function ComponentName() {}
135
+ {
136
+ pattern: /^[\s]*export\s+(?:default\s+)?function\s+([A-Z][a-zA-Z0-9_$]*)\s*\(/gm,
137
+ type: 'react_component'
138
+ }
139
+ ]
140
+
141
+ for (const { pattern, type } of patterns) {
142
+ let match
143
+ while ((match = pattern.exec(content)) !== null) {
144
+ const functionName = match[1]
145
+ const startIndex = match.index
146
+
147
+ // Find the line number
148
+ const lineNumber = content.substring(0, startIndex).split('\n').length
149
+
150
+ // Extract function body
151
+ const functionInfo = this.extractFunctionBody(content, startIndex, lines, lineNumber)
152
+
153
+ if (functionInfo && functionInfo.code.length > 10) { // Only include substantial functions
154
+ functions.push({
155
+ name: functionName,
156
+ type: type,
157
+ filePath: filePath,
158
+ startLine: lineNumber,
159
+ endLine: functionInfo.endLine,
160
+ code: functionInfo.code,
161
+ context: this.extractContext(content, functionInfo, filePath),
162
+ signature: match[0].trim()
163
+ })
164
+ }
165
+ }
166
+ }
167
+
168
+ return functions
169
+ }
170
+
171
+ /**
172
+ * Extract function body by finding matching braces
173
+ */
174
+ extractFunctionBody(content, startIndex, lines, startLine) {
175
+ // Find opening brace
176
+ let braceIndex = content.indexOf('{', startIndex)
177
+ if (braceIndex === -1) {
178
+ // Handle arrow functions without braces: const fn = () => expression
179
+ const lineEnd = content.indexOf('\n', startIndex)
180
+ if (lineEnd !== -1) {
181
+ const functionCode = content.substring(startIndex, lineEnd)
182
+ return {
183
+ code: functionCode,
184
+ endLine: startLine
185
+ }
186
+ }
187
+ return null
188
+ }
189
+
190
+ // Count braces to find matching closing brace
191
+ let braceCount = 1
192
+ let currentIndex = braceIndex + 1
193
+ let inString = false
194
+ let stringChar = null
195
+
196
+ while (currentIndex < content.length && braceCount > 0) {
197
+ const char = content[currentIndex]
198
+ const prevChar = content[currentIndex - 1]
199
+
200
+ // Handle string literals to avoid counting braces inside strings
201
+ if ((char === '"' || char === "'" || char === '`') && prevChar !== '\\') {
202
+ if (!inString) {
203
+ inString = true
204
+ stringChar = char
205
+ } else if (char === stringChar) {
206
+ inString = false
207
+ stringChar = null
208
+ }
209
+ }
210
+
211
+ if (!inString) {
212
+ if (char === '{') braceCount++
213
+ else if (char === '}') braceCount--
214
+ }
215
+
216
+ currentIndex++
217
+ }
218
+
219
+ if (braceCount !== 0) return null // Unmatched braces
220
+
221
+ // Extract the function code
222
+ const functionCode = content.substring(startIndex, currentIndex)
223
+ const endLine = startLine + functionCode.split('\n').length - 1
224
+
225
+ return {
226
+ code: functionCode.trim(),
227
+ endLine: endLine
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Extract relevant context for a function
233
+ */
234
+ extractContext(content, functionInfo, filePath) {
235
+ const context = {
236
+ imports: this.extractImports(content),
237
+ types: this.extractTypes(content),
238
+ dependencies: [],
239
+ calledFunctions: this.extractCalledFunctions(functionInfo.code)
240
+ }
241
+
242
+ return context
243
+ }
244
+
245
+ /**
246
+ * Extract import statements
247
+ */
248
+ extractImports(content) {
249
+ const imports = []
250
+ const importPattern = /import\s+(?:{[^}]+}|\*\s+as\s+\w+|\w+)\s+from\s+['"`]([^'"`]+)['"`]/g
251
+
252
+ let match
253
+ while ((match = importPattern.exec(content)) !== null) {
254
+ imports.push(match[1])
255
+ }
256
+
257
+ return imports
258
+ }
259
+
260
+ /**
261
+ * Extract type definitions
262
+ */
263
+ extractTypes(content) {
264
+ const types = []
265
+ const typePattern = /(?:type|interface)\s+([A-Z][a-zA-Z0-9]*)/g
266
+
267
+ let match
268
+ while ((match = typePattern.exec(content)) !== null) {
269
+ types.push(match[1])
270
+ }
271
+
272
+ return types
273
+ }
274
+
275
+ /**
276
+ * Extract function calls within code
277
+ */
278
+ extractCalledFunctions(code) {
279
+ const calls = []
280
+ const callPattern = /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g
281
+
282
+ let match
283
+ while ((match = callPattern.exec(code)) !== null) {
284
+ const funcName = match[1]
285
+ // Filter out common keywords and built-ins
286
+ if (!['if', 'for', 'while', 'switch', 'catch', 'console', 'Object', 'Array'].includes(funcName)) {
287
+ calls.push(funcName)
288
+ }
289
+ }
290
+
291
+ return [...new Set(calls)] // Remove duplicates
292
+ }
293
+
294
+ /**
295
+ * Create semantic chunks from extracted functions
296
+ */
297
+ createFunctionChunks(functions) {
298
+ const chunks = []
299
+
300
+ // Group functions by file and related functionality
301
+ const fileGroups = this.groupFunctionsByFile(functions)
302
+
303
+ for (const [filePath, fileFunctions] of Object.entries(fileGroups)) {
304
+ // Create individual function chunks
305
+ for (const func of fileFunctions) {
306
+ chunks.push({
307
+ name: `${func.name}`,
308
+ type: 'function',
309
+ subtype: func.type,
310
+ filePath: func.filePath,
311
+ functions: [func],
312
+ size: func.code.length,
313
+ complexity: this.calculateComplexity(func.code),
314
+ context: func.context,
315
+ purpose: this.determinePurpose(func),
316
+ tags: this.generateTags(func)
317
+ })
318
+ }
319
+ }
320
+
321
+ return chunks
322
+ }
323
+
324
+ /**
325
+ * Group functions by file
326
+ */
327
+ groupFunctionsByFile(functions) {
328
+ const groups = {}
329
+
330
+ for (const func of functions) {
331
+ if (!groups[func.filePath]) {
332
+ groups[func.filePath] = []
333
+ }
334
+ groups[func.filePath].push(func)
335
+ }
336
+
337
+ return groups
338
+ }
339
+
340
+ /**
341
+ * Calculate function complexity
342
+ */
343
+ calculateComplexity(code) {
344
+ const complexityIndicators = [
345
+ 'if', 'else', 'for', 'while', 'switch', 'case', 'try', 'catch',
346
+ '&&', '||', '?', ':', 'async', 'await'
347
+ ]
348
+
349
+ let complexity = 1 // Base complexity
350
+
351
+ for (const indicator of complexityIndicators) {
352
+ const count = (code.match(new RegExp(`\\b${indicator}\\b`, 'g')) || []).length
353
+ complexity += count
354
+ }
355
+
356
+ return {
357
+ score: complexity,
358
+ level: complexity < 5 ? 'low' : complexity < 15 ? 'medium' : 'high'
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Determine function purpose
364
+ */
365
+ determinePurpose(func) {
366
+ const name = func.name.toLowerCase()
367
+ const code = func.code.toLowerCase()
368
+
369
+ if (func.type === 'react_component') return 'React component'
370
+ if (name.startsWith('use') && func.type === 'function') return 'React hook'
371
+ if (name.includes('test') || name.includes('spec')) return 'Test function'
372
+ if (name.includes('get') || name.includes('fetch')) return 'Data retrieval'
373
+ if (name.includes('create') || name.includes('add')) return 'Data creation'
374
+ if (name.includes('update') || name.includes('edit')) return 'Data modification'
375
+ if (name.includes('delete') || name.includes('remove')) return 'Data deletion'
376
+ if (name.includes('validate') || name.includes('check')) return 'Validation'
377
+ if (code.includes('express') || code.includes('router')) return 'API endpoint'
378
+
379
+ return 'Utility function'
380
+ }
381
+
382
+ /**
383
+ * Generate tags for function
384
+ */
385
+ generateTags(func) {
386
+ const tags = []
387
+
388
+ tags.push(func.type)
389
+ if (func.context.imports.length > 0) tags.push('has-imports')
390
+ if (func.context.calledFunctions.length > 3) tags.push('complex-logic')
391
+ if (func.code.includes('async')) tags.push('async')
392
+ if (func.code.includes('export')) tags.push('exported')
393
+ if (func.name.match(/^[A-Z]/)) tags.push('component-style')
394
+
395
+ return tags
396
+ }
397
+
398
+ /**
399
+ * Fallback tree-sitter extraction (if available)
400
+ */
401
+ extractWithTreeSitter(content, filePath) {
402
+ // TODO: Implement tree-sitter extraction for functions
403
+ // For now, return empty to force regex fallback
404
+ return []
405
+ }
406
+ }