cntx-ui 3.0.7 → 3.0.8
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.
- package/lib/semantic-splitter.js +51 -8
- package/lib/treesitter-semantic-chunker.js +98 -26
- package/package.json +2 -1
- package/server.js +1 -1
package/lib/semantic-splitter.js
CHANGED
|
@@ -9,6 +9,7 @@ import { join, extname } from 'path'
|
|
|
9
9
|
import Parser from 'tree-sitter'
|
|
10
10
|
import JavaScript from 'tree-sitter-javascript'
|
|
11
11
|
import TypeScript from 'tree-sitter-typescript'
|
|
12
|
+
import Rust from 'tree-sitter-rust'
|
|
12
13
|
import HeuristicsManager from './heuristics-manager.js'
|
|
13
14
|
|
|
14
15
|
export default class SemanticSplitter {
|
|
@@ -24,11 +25,13 @@ export default class SemanticSplitter {
|
|
|
24
25
|
this.parsers = {
|
|
25
26
|
javascript: new Parser(),
|
|
26
27
|
typescript: new Parser(),
|
|
27
|
-
tsx: new Parser()
|
|
28
|
+
tsx: new Parser(),
|
|
29
|
+
rust: new Parser()
|
|
28
30
|
}
|
|
29
31
|
this.parsers.javascript.setLanguage(JavaScript)
|
|
30
32
|
this.parsers.typescript.setLanguage(TypeScript.typescript)
|
|
31
33
|
this.parsers.tsx.setLanguage(TypeScript.tsx)
|
|
34
|
+
this.parsers.rust.setLanguage(Rust)
|
|
32
35
|
|
|
33
36
|
this.heuristicsManager = new HeuristicsManager()
|
|
34
37
|
}
|
|
@@ -38,6 +41,7 @@ export default class SemanticSplitter {
|
|
|
38
41
|
switch (ext) {
|
|
39
42
|
case '.ts': return this.parsers.typescript
|
|
40
43
|
case '.tsx': return this.parsers.tsx
|
|
44
|
+
case '.rs': return this.parsers.rust
|
|
41
45
|
default: return this.parsers.javascript
|
|
42
46
|
}
|
|
43
47
|
}
|
|
@@ -98,7 +102,7 @@ export default class SemanticSplitter {
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
traverse(node, content, filePath, elements) {
|
|
101
|
-
// Detect Function Declarations
|
|
105
|
+
// Detect Function Declarations (JS/TS)
|
|
102
106
|
if (node.type === 'function_declaration' || node.type === 'method_definition' || node.type === 'arrow_function') {
|
|
103
107
|
const func = this.mapFunctionNode(node, content, filePath)
|
|
104
108
|
if (func && func.code.length > this.options.minFunctionSize) {
|
|
@@ -106,14 +110,39 @@ export default class SemanticSplitter {
|
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
|
|
113
|
+
// Detect Rust function items
|
|
114
|
+
if (node.type === 'function_item') {
|
|
115
|
+
const func = this.mapFunctionNode(node, content, filePath)
|
|
116
|
+
if (func && func.code.length > this.options.minFunctionSize) {
|
|
117
|
+
elements.functions.push(func)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
109
121
|
// Detect Type Definitions (TS)
|
|
110
122
|
if (node.type === 'interface_declaration' || node.type === 'type_alias_declaration') {
|
|
111
123
|
const typeDef = this.mapTypeNode(node, content, filePath)
|
|
112
124
|
if (typeDef) elements.types.push(typeDef)
|
|
113
125
|
}
|
|
114
126
|
|
|
127
|
+
// Detect Rust type definitions
|
|
128
|
+
if (node.type === 'struct_item' || node.type === 'enum_item' || node.type === 'trait_item') {
|
|
129
|
+
const typeDef = this.mapTypeNode(node, content, filePath)
|
|
130
|
+
if (typeDef) elements.types.push(typeDef)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Detect Rust impl blocks — traverse into body for methods
|
|
134
|
+
if (node.type === 'impl_item') {
|
|
135
|
+
const body = node.childForFieldName('body')
|
|
136
|
+
if (body) {
|
|
137
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
138
|
+
this.traverse(body.namedChild(i), content, filePath, elements)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return // Don't recurse again below
|
|
142
|
+
}
|
|
143
|
+
|
|
115
144
|
// Recurse unless we've already captured the block (like a function body)
|
|
116
|
-
if (node.type !== 'function_declaration' && node.type !== 'method_definition') {
|
|
145
|
+
if (node.type !== 'function_declaration' && node.type !== 'method_definition' && node.type !== 'function_item') {
|
|
117
146
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
118
147
|
this.traverse(node.namedChild(i), content, filePath, elements)
|
|
119
148
|
}
|
|
@@ -124,7 +153,7 @@ export default class SemanticSplitter {
|
|
|
124
153
|
let name = 'anonymous';
|
|
125
154
|
|
|
126
155
|
// Find name identifier based on node type
|
|
127
|
-
if (node.type === 'function_declaration' || node.type === 'method_definition') {
|
|
156
|
+
if (node.type === 'function_declaration' || node.type === 'method_definition' || node.type === 'function_item') {
|
|
128
157
|
const nameNode = node.childForFieldName('name');
|
|
129
158
|
if (nameNode) name = content.slice(nameNode.startIndex, nameNode.endIndex);
|
|
130
159
|
} else if (node.type === 'arrow_function') {
|
|
@@ -175,10 +204,10 @@ export default class SemanticSplitter {
|
|
|
175
204
|
|
|
176
205
|
extractImports(root, content, filePath) {
|
|
177
206
|
const imports = []
|
|
178
|
-
// Simple traversal for import statements
|
|
207
|
+
// Simple traversal for import/use statements
|
|
179
208
|
for (let i = 0; i < root.namedChildCount; i++) {
|
|
180
209
|
const node = root.namedChild(i)
|
|
181
|
-
if (node.type === 'import_statement') {
|
|
210
|
+
if (node.type === 'import_statement' || node.type === 'use_declaration') {
|
|
182
211
|
imports.push({
|
|
183
212
|
statement: content.slice(node.startIndex, node.endIndex),
|
|
184
213
|
filePath
|
|
@@ -189,6 +218,11 @@ export default class SemanticSplitter {
|
|
|
189
218
|
}
|
|
190
219
|
|
|
191
220
|
isExported(node) {
|
|
221
|
+
// Rust: check for visibility_modifier (pub) as direct child
|
|
222
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
223
|
+
if (node.namedChild(i).type === 'visibility_modifier') return true
|
|
224
|
+
}
|
|
225
|
+
// JS/TS: check for export_statement ancestor
|
|
192
226
|
let current = node
|
|
193
227
|
while (current) {
|
|
194
228
|
if (current.type === 'export_statement') return true
|
|
@@ -250,7 +284,16 @@ export default class SemanticSplitter {
|
|
|
250
284
|
}
|
|
251
285
|
|
|
252
286
|
isImportRelevant(importStatement, functionCode) {
|
|
253
|
-
//
|
|
287
|
+
// Rust use statements: use std::collections::HashMap;
|
|
288
|
+
const useMatch = importStatement.match(/^use\s+(.+);?\s*$/)
|
|
289
|
+
if (useMatch) {
|
|
290
|
+
const path = useMatch[1]
|
|
291
|
+
// Extract the last segment (the actual imported name)
|
|
292
|
+
const segments = path.replace(/[{}]/g, '').split('::')
|
|
293
|
+
const lastSegment = segments[segments.length - 1].trim()
|
|
294
|
+
return functionCode.includes(lastSegment)
|
|
295
|
+
}
|
|
296
|
+
// JS/TS import statements
|
|
254
297
|
const match = importStatement.match(/import\s+(?:\{([^}]+)\}|(\w+))/i)
|
|
255
298
|
if (!match) return false
|
|
256
299
|
const importedNames = match[1] ? match[1].split(',').map(n => n.trim()) : [match[2]]
|
|
@@ -258,7 +301,7 @@ export default class SemanticSplitter {
|
|
|
258
301
|
}
|
|
259
302
|
|
|
260
303
|
calculateComplexity(code) {
|
|
261
|
-
const indicators = ['if', 'else', 'for', 'while', 'switch', 'case', 'catch', '?', '&&', '||'];
|
|
304
|
+
const indicators = ['if', 'else', 'for', 'while', 'switch', 'case', 'catch', '?', '&&', '||', 'match', 'loop', 'unsafe', 'unwrap', 'expect'];
|
|
262
305
|
let score = 1;
|
|
263
306
|
indicators.forEach(ind => {
|
|
264
307
|
// Escape special regex characters
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Treesitter-based Semantic Chunker for JavaScript/TypeScript Files
|
|
2
|
+
* Treesitter-based Semantic Chunker for JavaScript/TypeScript and Rust Files
|
|
3
3
|
* Uses tree-sitter for true AST-based code analysis and semantic chunking
|
|
4
|
-
* Supports JS/TS/JSX/TSX with equal treatment
|
|
4
|
+
* Supports JS/TS/JSX/TSX and Rust with equal treatment
|
|
5
5
|
* Node ecosystem focus: React components, Express APIs, CLI tools, utilities
|
|
6
|
+
* Rust ecosystem focus: standalone functions, structs, enums, traits
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { readFileSync, existsSync } from 'fs'
|
|
9
10
|
import { extname, basename, dirname, relative, join } from 'path'
|
|
10
|
-
import glob from 'glob'
|
|
11
|
-
import { promisify } from 'util'
|
|
11
|
+
import { glob } from 'glob'
|
|
12
12
|
import Parser from 'tree-sitter'
|
|
13
13
|
import JavaScript from 'tree-sitter-javascript'
|
|
14
14
|
import TypeScript from 'tree-sitter-typescript'
|
|
15
|
-
|
|
16
|
-
const globAsync = promisify(glob)
|
|
15
|
+
import Rust from 'tree-sitter-rust'
|
|
17
16
|
|
|
18
17
|
class TreesitterSemanticChunker {
|
|
19
18
|
constructor(options = {}) {
|
|
@@ -32,7 +31,7 @@ class TreesitterSemanticChunker {
|
|
|
32
31
|
this.parsers = {}
|
|
33
32
|
this.initializeParsers()
|
|
34
33
|
|
|
35
|
-
// Semantic patterns for Node ecosystem
|
|
34
|
+
// Semantic patterns for Node/Rust ecosystem
|
|
36
35
|
this.semanticPatterns = {
|
|
37
36
|
reactComponent: this.isReactComponent.bind(this),
|
|
38
37
|
reactHook: this.isReactHook.bind(this),
|
|
@@ -61,6 +60,10 @@ class TreesitterSemanticChunker {
|
|
|
61
60
|
// TSX parser
|
|
62
61
|
this.parsers.tsx = new Parser()
|
|
63
62
|
this.parsers.tsx.setLanguage(TypeScript.tsx)
|
|
63
|
+
|
|
64
|
+
// Rust parser
|
|
65
|
+
this.parsers.rust = new Parser()
|
|
66
|
+
this.parsers.rust.setLanguage(Rust)
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/**
|
|
@@ -71,6 +74,7 @@ class TreesitterSemanticChunker {
|
|
|
71
74
|
switch (ext) {
|
|
72
75
|
case '.ts': return this.parsers.typescript
|
|
73
76
|
case '.tsx': return this.parsers.tsx
|
|
77
|
+
case '.rs': return this.parsers.rust
|
|
74
78
|
case '.js':
|
|
75
79
|
case '.jsx':
|
|
76
80
|
default: return this.parsers.javascript
|
|
@@ -80,7 +84,7 @@ class TreesitterSemanticChunker {
|
|
|
80
84
|
/**
|
|
81
85
|
* Main entry point - analyze files and create semantic chunks
|
|
82
86
|
*/
|
|
83
|
-
async analyzeProject(projectPath, patterns = ['**/*.{js,jsx,ts,tsx}']) {
|
|
87
|
+
async analyzeProject(projectPath, patterns = ['**/*.{js,jsx,ts,tsx,rs}']) {
|
|
84
88
|
console.log('🔍 Starting treesitter-based semantic analysis...')
|
|
85
89
|
|
|
86
90
|
const files = await this.findFiles(projectPath, patterns)
|
|
@@ -115,7 +119,7 @@ class TreesitterSemanticChunker {
|
|
|
115
119
|
const files = []
|
|
116
120
|
|
|
117
121
|
for (const pattern of patterns) {
|
|
118
|
-
const matches = await
|
|
122
|
+
const matches = await glob(pattern, {
|
|
119
123
|
cwd: projectPath,
|
|
120
124
|
ignore: [
|
|
121
125
|
'node_modules/**', 'dist/**', 'build/**', '.git/**',
|
|
@@ -186,11 +190,6 @@ class TreesitterSemanticChunker {
|
|
|
186
190
|
// Use simple string parsing (confirmed working in tests)
|
|
187
191
|
tree = parser.parse(content)
|
|
188
192
|
rootNode = tree.rootNode
|
|
189
|
-
|
|
190
|
-
// Check for parse errors
|
|
191
|
-
if (rootNode.hasError()) {
|
|
192
|
-
throw new Error('Parse error in file')
|
|
193
|
-
}
|
|
194
193
|
} catch (error) {
|
|
195
194
|
throw new Error(`Tree-sitter parse failed: ${error.message}`)
|
|
196
195
|
}
|
|
@@ -239,7 +238,7 @@ class TreesitterSemanticChunker {
|
|
|
239
238
|
extractFunctions(rootNode, content) {
|
|
240
239
|
const functions = []
|
|
241
240
|
|
|
242
|
-
// Function declarations
|
|
241
|
+
// JS/TS Function declarations
|
|
243
242
|
const functionDeclarations = this.queryNode(rootNode, '(function_declaration name: (identifier) @name)')
|
|
244
243
|
functions.push(...functionDeclarations.map(capture => ({
|
|
245
244
|
name: this.getNodeText(capture.node, content),
|
|
@@ -249,7 +248,7 @@ class TreesitterSemanticChunker {
|
|
|
249
248
|
isExported: this.isNodeExported(capture.node)
|
|
250
249
|
})))
|
|
251
250
|
|
|
252
|
-
// Arrow functions
|
|
251
|
+
// JS/TS Arrow functions
|
|
253
252
|
const arrowFunctions = this.queryNode(rootNode, '(variable_declarator name: (identifier) @name value: (arrow_function))')
|
|
254
253
|
functions.push(...arrowFunctions.map(capture => ({
|
|
255
254
|
name: this.getNodeText(capture.node, content),
|
|
@@ -259,7 +258,7 @@ class TreesitterSemanticChunker {
|
|
|
259
258
|
isExported: this.isNodeExported(capture.node.parent.parent)
|
|
260
259
|
})))
|
|
261
260
|
|
|
262
|
-
// Method definitions
|
|
261
|
+
// JS/TS Method definitions
|
|
263
262
|
const methods = this.queryNode(rootNode, '(method_definition name: (property_name) @name)')
|
|
264
263
|
functions.push(...methods.map(capture => ({
|
|
265
264
|
name: this.getNodeText(capture.node, content),
|
|
@@ -268,6 +267,16 @@ class TreesitterSemanticChunker {
|
|
|
268
267
|
endPosition: capture.node.endPosition,
|
|
269
268
|
isExported: false // methods are part of classes
|
|
270
269
|
})))
|
|
270
|
+
|
|
271
|
+
// Rust function items
|
|
272
|
+
const rustFunctions = this.queryNode(rootNode, '(function_item name: (identifier) @name)')
|
|
273
|
+
functions.push(...rustFunctions.map(capture => ({
|
|
274
|
+
name: this.getNodeText(capture.node, content),
|
|
275
|
+
type: 'function_item',
|
|
276
|
+
startPosition: capture.node.startPosition,
|
|
277
|
+
endPosition: capture.node.endPosition,
|
|
278
|
+
isExported: this.isNodeExported(capture.node)
|
|
279
|
+
})))
|
|
271
280
|
|
|
272
281
|
return functions
|
|
273
282
|
}
|
|
@@ -318,6 +327,7 @@ class TreesitterSemanticChunker {
|
|
|
318
327
|
extractImports(rootNode, content) {
|
|
319
328
|
const imports = []
|
|
320
329
|
|
|
330
|
+
// JS/TS imports
|
|
321
331
|
const importStatements = this.queryNode(rootNode, '(import_statement source: (string) @source)')
|
|
322
332
|
imports.push(...importStatements.map(capture => {
|
|
323
333
|
const source = this.getNodeText(capture.node, content).replace(/['"]/g, '')
|
|
@@ -329,6 +339,19 @@ class TreesitterSemanticChunker {
|
|
|
329
339
|
importedNames: this.extractImportedNames(capture.node.parent, content)
|
|
330
340
|
}
|
|
331
341
|
}))
|
|
342
|
+
|
|
343
|
+
// Rust use declarations
|
|
344
|
+
const rustUseStatements = this.queryNode(rootNode, '(use_declaration)')
|
|
345
|
+
imports.push(...rustUseStatements.map(capture => {
|
|
346
|
+
const statement = this.getNodeText(capture.node, content)
|
|
347
|
+
return {
|
|
348
|
+
source: 'rust',
|
|
349
|
+
statement,
|
|
350
|
+
isRelative: statement.includes('self::') || statement.includes('super::'),
|
|
351
|
+
isExternal: !statement.includes('crate::') && !statement.includes('self::') && !statement.includes('super::'),
|
|
352
|
+
importedNames: [] // Complexity of parsing Rust use paths is high for this simple chunker
|
|
353
|
+
}
|
|
354
|
+
}))
|
|
332
355
|
|
|
333
356
|
return imports
|
|
334
357
|
}
|
|
@@ -339,7 +362,7 @@ class TreesitterSemanticChunker {
|
|
|
339
362
|
extractExports(rootNode, content) {
|
|
340
363
|
const exports = []
|
|
341
364
|
|
|
342
|
-
// Export declarations
|
|
365
|
+
// Export declarations (JS/TS)
|
|
343
366
|
const exportDeclarations = this.queryNode(rootNode, '(export_statement)')
|
|
344
367
|
exports.push(...exportDeclarations.map(capture => {
|
|
345
368
|
const exportNode = capture.node
|
|
@@ -405,7 +428,7 @@ class TreesitterSemanticChunker {
|
|
|
405
428
|
const types = []
|
|
406
429
|
|
|
407
430
|
try {
|
|
408
|
-
// Interface declarations
|
|
431
|
+
// JS/TS Interface declarations
|
|
409
432
|
const interfaces = this.queryNode(rootNode, '(interface_declaration name: (type_identifier) @name)')
|
|
410
433
|
types.push(...interfaces.map(capture => ({
|
|
411
434
|
name: this.getNodeText(capture.node, content),
|
|
@@ -415,7 +438,7 @@ class TreesitterSemanticChunker {
|
|
|
415
438
|
isExported: this.isNodeExported(capture.node.parent)
|
|
416
439
|
})))
|
|
417
440
|
|
|
418
|
-
// Type alias declarations
|
|
441
|
+
// JS/TS Type alias declarations
|
|
419
442
|
const typeAliases = this.queryNode(rootNode, '(type_alias_declaration name: (type_identifier) @name)')
|
|
420
443
|
types.push(...typeAliases.map(capture => ({
|
|
421
444
|
name: this.getNodeText(capture.node, content),
|
|
@@ -424,8 +447,38 @@ class TreesitterSemanticChunker {
|
|
|
424
447
|
endPosition: capture.node.endPosition,
|
|
425
448
|
isExported: this.isNodeExported(capture.node.parent)
|
|
426
449
|
})))
|
|
450
|
+
|
|
451
|
+
// Rust struct definitions
|
|
452
|
+
const structs = this.queryNode(rootNode, '(struct_item name: (type_identifier) @name)')
|
|
453
|
+
types.push(...structs.map(capture => ({
|
|
454
|
+
name: this.getNodeText(capture.node, content),
|
|
455
|
+
type: 'struct',
|
|
456
|
+
startPosition: capture.node.startPosition,
|
|
457
|
+
endPosition: capture.node.endPosition,
|
|
458
|
+
isExported: this.isNodeExported(capture.node)
|
|
459
|
+
})))
|
|
460
|
+
|
|
461
|
+
// Rust enum definitions
|
|
462
|
+
const enums = this.queryNode(rootNode, '(enum_item name: (type_identifier) @name)')
|
|
463
|
+
types.push(...enums.map(capture => ({
|
|
464
|
+
name: this.getNodeText(capture.node, content),
|
|
465
|
+
type: 'enum',
|
|
466
|
+
startPosition: capture.node.startPosition,
|
|
467
|
+
endPosition: capture.node.endPosition,
|
|
468
|
+
isExported: this.isNodeExported(capture.node)
|
|
469
|
+
})))
|
|
470
|
+
|
|
471
|
+
// Rust trait definitions
|
|
472
|
+
const traits = this.queryNode(rootNode, '(trait_item name: (type_identifier) @name)')
|
|
473
|
+
types.push(...traits.map(capture => ({
|
|
474
|
+
name: this.getNodeText(capture.node, content),
|
|
475
|
+
type: 'trait',
|
|
476
|
+
startPosition: capture.node.startPosition,
|
|
477
|
+
endPosition: capture.node.endPosition,
|
|
478
|
+
isExported: this.isNodeExported(capture.node)
|
|
479
|
+
})))
|
|
427
480
|
} catch (error) {
|
|
428
|
-
// TypeScript types might not be available
|
|
481
|
+
// TypeScript/Rust types might not be available
|
|
429
482
|
}
|
|
430
483
|
|
|
431
484
|
return types
|
|
@@ -576,6 +629,18 @@ class TreesitterSemanticChunker {
|
|
|
576
629
|
return hasTypeFileName || hasOnlyTypes
|
|
577
630
|
}
|
|
578
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Semantic pattern: UI Component (generic for any language)
|
|
634
|
+
*/
|
|
635
|
+
isUiComponent(rootNode, content, filePath) {
|
|
636
|
+
// JS/TS logic
|
|
637
|
+
if (extname(filePath).match(/\.(jsx|tsx|js|ts)$/)) {
|
|
638
|
+
return this.isReactComponent(rootNode, content, filePath)
|
|
639
|
+
}
|
|
640
|
+
// TODO: Add generic patterns for other languages (Rust templates, etc.)
|
|
641
|
+
return false
|
|
642
|
+
}
|
|
643
|
+
|
|
579
644
|
/**
|
|
580
645
|
* Semantic pattern: Config Module
|
|
581
646
|
*/
|
|
@@ -583,7 +648,7 @@ class TreesitterSemanticChunker {
|
|
|
583
648
|
const fileName = basename(filePath).toLowerCase()
|
|
584
649
|
const hasConfigName = fileName.includes('config') || fileName.includes('setting')
|
|
585
650
|
|
|
586
|
-
const hasConfigPatterns = content.includes('module.exports') || content.includes('export default')
|
|
651
|
+
const hasConfigPatterns = content.includes('module.exports') || content.includes('export default') || content.includes('Cargo.toml')
|
|
587
652
|
const hasConfigObject = /\{[\s\S]*\}/.test(content) && !/function|class/.test(content)
|
|
588
653
|
|
|
589
654
|
return hasConfigName && (hasConfigPatterns || hasConfigObject)
|
|
@@ -630,11 +695,13 @@ class TreesitterSemanticChunker {
|
|
|
630
695
|
if (content.includes('react')) patterns.push('react')
|
|
631
696
|
if (content.includes('express')) patterns.push('express')
|
|
632
697
|
if (content.includes('typescript')) patterns.push('typescript')
|
|
698
|
+
if (content.includes('cargo') || extname(content) === '.rs') patterns.push('rust')
|
|
633
699
|
|
|
634
700
|
// Architecture patterns
|
|
635
|
-
if (content.includes('async') && content.includes('await')) patterns.push('async-await')
|
|
701
|
+
if (content.includes('async') && (content.includes('await') || content.includes('.await'))) patterns.push('async-await')
|
|
636
702
|
if (content.includes('Promise')) patterns.push('promises')
|
|
637
703
|
if (content.includes('class') && content.includes('extends')) patterns.push('inheritance')
|
|
704
|
+
if (content.includes('unsafe')) patterns.push('unsafe-code')
|
|
638
705
|
|
|
639
706
|
// Design patterns
|
|
640
707
|
const functions = this.extractFunctions(rootNode, content)
|
|
@@ -996,10 +1063,14 @@ class TreesitterSemanticChunker {
|
|
|
996
1063
|
}
|
|
997
1064
|
|
|
998
1065
|
isNodeExported(node) {
|
|
999
|
-
//
|
|
1066
|
+
// Rust: check for visibility_modifier (pub) as direct child
|
|
1067
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1068
|
+
if (node.namedChild(i).type === 'visibility_modifier') return true
|
|
1069
|
+
}
|
|
1070
|
+
// JS/TS: check for export_statement ancestor or parent
|
|
1000
1071
|
let parent = node.parent
|
|
1001
1072
|
while (parent) {
|
|
1002
|
-
if (parent.type === 'export_statement') {
|
|
1073
|
+
if (parent.type === 'export_statement' || parent.type === 'export_declaration') {
|
|
1003
1074
|
return true
|
|
1004
1075
|
}
|
|
1005
1076
|
parent = parent.parent
|
|
@@ -1048,7 +1119,8 @@ class TreesitterSemanticChunker {
|
|
|
1048
1119
|
let complexity = 1
|
|
1049
1120
|
|
|
1050
1121
|
const complexityNodes = ['if_statement', 'while_statement', 'for_statement',
|
|
1051
|
-
'switch_statement', 'try_statement', 'catch_clause'
|
|
1122
|
+
'switch_statement', 'try_statement', 'catch_clause',
|
|
1123
|
+
'match_arm', 'loop_expression']
|
|
1052
1124
|
|
|
1053
1125
|
const traverse = (node) => {
|
|
1054
1126
|
if (complexityNodes.includes(node.type)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cntx-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.8",
|
|
5
5
|
"description": "Autonomous Repository Intelligence engine with web UI and MCP server. Unified semantic code understanding, local RAG, and agent working memory.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"repository-intelligence",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"glob": "^9.0.0",
|
|
50
50
|
"tree-sitter": "^0.21.1",
|
|
51
51
|
"tree-sitter-javascript": "^0.23.1",
|
|
52
|
+
"tree-sitter-rust": "^0.21.0",
|
|
52
53
|
"tree-sitter-typescript": "^0.23.2",
|
|
53
54
|
"ws": "^8.13.0"
|
|
54
55
|
}
|
package/server.js
CHANGED
|
@@ -361,7 +361,7 @@ export class CntxServer {
|
|
|
361
361
|
|
|
362
362
|
// 2. Perform fresh analysis if DB is empty
|
|
363
363
|
try {
|
|
364
|
-
const supportedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.mjs'];
|
|
364
|
+
const supportedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.rs'];
|
|
365
365
|
const files = this.fileSystemManager.getAllFiles()
|
|
366
366
|
.filter(f => supportedExtensions.includes(extname(f).toLowerCase()))
|
|
367
367
|
.map(f => relative(this.CWD, f));
|