cntx-ui 3.0.2 → 3.0.4
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/file-system-manager.js +1 -1
- package/lib/heuristics-manager.js +4 -2
- package/lib/semantic-splitter.js +17 -42
- package/lib/simple-vector-store.js +20 -10
- package/package.json +1 -1
- package/server.js +7 -3
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles file operations, pattern matching, and directory traversal
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { readdirSync, statSync, existsSync, watch } from 'fs';
|
|
6
|
+
import { readdirSync, readFileSync, statSync, existsSync, watch } from 'fs';
|
|
7
7
|
import { join, relative, extname, basename, dirname } from 'path';
|
|
8
8
|
|
|
9
9
|
export default class FileSystemManager {
|
|
@@ -127,7 +127,7 @@ export default class HeuristicsManager {
|
|
|
127
127
|
const domains = new Set();
|
|
128
128
|
const name = func.name.toLowerCase();
|
|
129
129
|
const path = (func.pathParts || []).join('/').toLowerCase();
|
|
130
|
-
const imports = func.includes?.imports || [];
|
|
130
|
+
const imports = (func.includes?.imports || []).filter(i => typeof i === 'string');
|
|
131
131
|
|
|
132
132
|
// Path-based domains
|
|
133
133
|
if (path.includes('auth')) domains.add('authentication');
|
|
@@ -330,7 +330,9 @@ export default class HeuristicsManager {
|
|
|
330
330
|
if (condition.includes('chunk.imports.includes(')) {
|
|
331
331
|
const importMatch = condition.match(/chunk\.imports\.includes\(['"]([^'"]+)['"]\)/)
|
|
332
332
|
if (importMatch && func.includes?.imports) {
|
|
333
|
-
return func.includes.imports
|
|
333
|
+
return func.includes.imports
|
|
334
|
+
.filter(i => typeof i === 'string')
|
|
335
|
+
.some(imp => imp.includes(importMatch[1]))
|
|
334
336
|
}
|
|
335
337
|
}
|
|
336
338
|
|
package/lib/semantic-splitter.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
import { readFileSync, existsSync } from 'fs'
|
|
8
8
|
import { join, extname } from 'path'
|
|
9
|
-
import glob from 'glob'
|
|
10
9
|
import Parser from 'tree-sitter'
|
|
11
10
|
import JavaScript from 'tree-sitter-javascript'
|
|
12
11
|
import TypeScript from 'tree-sitter-typescript'
|
|
@@ -45,18 +44,14 @@ export default class SemanticSplitter {
|
|
|
45
44
|
|
|
46
45
|
/**
|
|
47
46
|
* Main entry point - extract semantic chunks from project
|
|
47
|
+
* Now accepts a pre-filtered list of files from FileSystemManager
|
|
48
48
|
*/
|
|
49
|
-
async extractSemanticChunks(projectPath,
|
|
50
|
-
console.log('🔪 Starting surgical semantic splitting via tree-sitter...')
|
|
51
|
-
console.log(`📂 Project path: ${projectPath}`)
|
|
52
|
-
console.log(`📋 Patterns: ${patterns}`);
|
|
49
|
+
async extractSemanticChunks(projectPath, files = [], bundleConfig = null) {
|
|
50
|
+
console.log('🔪 Starting surgical semantic splitting via tree-sitter...')
|
|
51
|
+
console.log(`📂 Project path: ${projectPath}`)
|
|
53
52
|
|
|
54
|
-
this.bundleConfig = bundleConfig
|
|
55
|
-
|
|
56
|
-
console.log(`📁 Found ${files.length} files to split`);
|
|
57
|
-
if (files.length > 0) {
|
|
58
|
-
console.log('📄 Sample files:', files.slice(0, 5));
|
|
59
|
-
}
|
|
53
|
+
this.bundleConfig = bundleConfig
|
|
54
|
+
console.log(`📁 Processing ${files.length} filtered files`)
|
|
60
55
|
|
|
61
56
|
const allChunks = []
|
|
62
57
|
|
|
@@ -74,48 +69,32 @@ export default class SemanticSplitter {
|
|
|
74
69
|
summary: {
|
|
75
70
|
totalFiles: files.length,
|
|
76
71
|
totalChunks: allChunks.length,
|
|
77
|
-
averageSize: allChunks.reduce((sum, c) => sum + c.code.length, 0) / allChunks.length
|
|
72
|
+
averageSize: allChunks.length > 0 ? allChunks.reduce((sum, c) => sum + c.code.length, 0) / allChunks.length : 0
|
|
78
73
|
},
|
|
79
74
|
chunks: allChunks
|
|
80
75
|
}
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
findFiles(projectPath, patterns) {
|
|
84
|
-
const files = []
|
|
85
|
-
for (const pattern of patterns) {
|
|
86
|
-
const matches = glob.sync(pattern, {
|
|
87
|
-
cwd: projectPath,
|
|
88
|
-
ignore: ['node_modules/**', 'dist/**', '.git/**', '*.test.*', '*.spec.*']
|
|
89
|
-
})
|
|
90
|
-
files.push(...matches)
|
|
91
|
-
}
|
|
92
|
-
return [...new Set(files)]
|
|
93
|
-
}
|
|
94
|
-
|
|
95
78
|
processFile(relativePath, projectPath) {
|
|
96
|
-
const fullPath = join(projectPath, relativePath)
|
|
97
|
-
if (!existsSync(fullPath)) return []
|
|
79
|
+
const fullPath = join(projectPath, relativePath)
|
|
80
|
+
if (!existsSync(fullPath)) return []
|
|
98
81
|
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const root = tree.rootNode;
|
|
82
|
+
const content = readFileSync(fullPath, 'utf8')
|
|
83
|
+
const parser = this.getParser(relativePath)
|
|
84
|
+
const tree = parser.parse(content)
|
|
85
|
+
const root = tree.rootNode
|
|
104
86
|
|
|
105
87
|
const elements = {
|
|
106
88
|
functions: [],
|
|
107
89
|
types: [],
|
|
108
90
|
imports: this.extractImports(root, content, relativePath)
|
|
109
|
-
}
|
|
91
|
+
}
|
|
110
92
|
|
|
111
93
|
// Traverse AST for functions and types
|
|
112
|
-
this.traverse(root, content, relativePath, elements)
|
|
94
|
+
this.traverse(root, content, relativePath, elements)
|
|
113
95
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// console.log(` ✅ Found ${chunks.length} chunks in ${relativePath}`);
|
|
117
|
-
// }
|
|
118
|
-
return chunks;
|
|
96
|
+
// Create chunks from elements
|
|
97
|
+
return this.createChunks(elements, content, relativePath)
|
|
119
98
|
}
|
|
120
99
|
|
|
121
100
|
traverse(node, content, filePath, elements) {
|
|
@@ -235,10 +214,6 @@ export default class SemanticSplitter {
|
|
|
235
214
|
const technicalPatterns = this.heuristicsManager.inferTechnicalPatterns(heuristicContext);
|
|
236
215
|
const tags = this.generateTags(func);
|
|
237
216
|
|
|
238
|
-
// if (businessDomain.length > 0) {
|
|
239
|
-
// console.log(` 💎 ${func.name}: Domains: [${businessDomain}], Patterns: [${technicalPatterns}]`);
|
|
240
|
-
// }
|
|
241
|
-
|
|
242
217
|
let chunkCode = '';
|
|
243
218
|
if (this.options.includeContext) {
|
|
244
219
|
const relevantImports = elements.imports
|
|
@@ -54,20 +54,30 @@ export default class SimpleVectorStore {
|
|
|
54
54
|
const queryEmbedding = await this.generateEmbedding(query);
|
|
55
55
|
|
|
56
56
|
// Load all embeddings from DB
|
|
57
|
-
// Optimization: In a huge codebase, we'd use a real vector DB or FAISS
|
|
58
|
-
// For now, SQLite + Manual Cosine Similarity is fine for local repos
|
|
59
57
|
const rows = this.db.db.prepare('SELECT chunk_id, embedding FROM vector_embeddings WHERE model_name = ?').all(this.modelName);
|
|
60
58
|
|
|
61
59
|
const results = [];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
const batchSize = 100;
|
|
61
|
+
|
|
62
|
+
// Process in batches to prevent blocking the event loop
|
|
63
|
+
for (let i = 0; i < rows.length; i += batchSize) {
|
|
64
|
+
const batch = rows.slice(i, i + batchSize);
|
|
65
|
+
|
|
66
|
+
for (const row of batch) {
|
|
67
|
+
const embedding = new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 4);
|
|
68
|
+
const similarity = this.cosineSimilarity(queryEmbedding, embedding);
|
|
69
|
+
|
|
70
|
+
if (similarity >= threshold) {
|
|
71
|
+
results.push({
|
|
72
|
+
chunkId: row.chunk_id,
|
|
73
|
+
similarity
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
similarity
|
|
70
|
-
});
|
|
78
|
+
// Give other tasks a chance to run
|
|
79
|
+
if (i + batchSize < rows.length) {
|
|
80
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
83
|
|
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.4",
|
|
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",
|
package/server.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { createServer } from 'http';
|
|
7
|
-
import { join, dirname } from 'path';
|
|
7
|
+
import { join, dirname, relative, extname } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, cpSync } from 'fs';
|
|
10
10
|
import * as fs from 'fs';
|
|
@@ -435,13 +435,17 @@ export class CntxServer {
|
|
|
435
435
|
|
|
436
436
|
// 2. Perform fresh analysis if DB is empty
|
|
437
437
|
try {
|
|
438
|
-
const
|
|
438
|
+
const supportedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.mjs'];
|
|
439
|
+
const files = this.fileSystemManager.getAllFiles()
|
|
440
|
+
.filter(f => supportedExtensions.includes(extname(f).toLowerCase()))
|
|
441
|
+
.map(f => relative(this.CWD, f));
|
|
442
|
+
|
|
439
443
|
let bundleConfig = null;
|
|
440
444
|
if (existsSync(this.configManager.CONFIG_FILE)) {
|
|
441
445
|
bundleConfig = JSON.parse(readFileSync(this.configManager.CONFIG_FILE, 'utf8'));
|
|
442
446
|
}
|
|
443
447
|
|
|
444
|
-
this.semanticCache = await this.semanticSplitter.extractSemanticChunks(this.CWD,
|
|
448
|
+
this.semanticCache = await this.semanticSplitter.extractSemanticChunks(this.CWD, files, bundleConfig);
|
|
445
449
|
this.lastSemanticAnalysis = Date.now();
|
|
446
450
|
|
|
447
451
|
// 3. Persist chunks to SQLite immediately
|