cntx-ui 3.0.7 → 3.0.9
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/dist/bin/cntx-ui.js +70 -0
- package/dist/lib/agent-runtime.js +269 -0
- package/dist/lib/agent-tools.js +162 -0
- package/dist/lib/api-router.js +387 -0
- package/dist/lib/bundle-manager.js +236 -0
- package/dist/lib/configuration-manager.js +230 -0
- package/dist/lib/database-manager.js +277 -0
- package/dist/lib/file-system-manager.js +305 -0
- package/dist/lib/function-level-chunker.js +144 -0
- package/dist/lib/heuristics-manager.js +491 -0
- package/dist/lib/mcp-server.js +159 -0
- package/dist/lib/mcp-transport.js +10 -0
- package/dist/lib/semantic-splitter.js +335 -0
- package/dist/lib/simple-vector-store.js +98 -0
- package/dist/lib/treesitter-semantic-chunker.js +277 -0
- package/dist/lib/websocket-manager.js +268 -0
- package/dist/server.js +225 -0
- package/package.json +18 -8
- package/bin/cntx-ui-mcp.sh +0 -3
- package/bin/cntx-ui.js +0 -123
- package/lib/agent-runtime.js +0 -371
- package/lib/agent-tools.js +0 -370
- package/lib/api-router.js +0 -1026
- package/lib/bundle-manager.js +0 -326
- package/lib/configuration-manager.js +0 -760
- package/lib/database-manager.js +0 -397
- package/lib/file-system-manager.js +0 -489
- package/lib/function-level-chunker.js +0 -406
- package/lib/heuristics-manager.js +0 -529
- package/lib/mcp-server.js +0 -1380
- package/lib/mcp-transport.js +0 -97
- package/lib/semantic-splitter.js +0 -304
- package/lib/simple-vector-store.js +0 -108
- package/lib/treesitter-semantic-chunker.js +0 -1485
- package/lib/websocket-manager.js +0 -470
- package/server.js +0 -687
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File System Manager for cntx-ui
|
|
3
|
+
* Handles file operations, pattern matching, and directory traversal
|
|
4
|
+
*/
|
|
5
|
+
import { readdirSync, readFileSync, statSync, existsSync, watch } from 'fs';
|
|
6
|
+
import { join, relative, extname, basename } from 'path';
|
|
7
|
+
export default class FileSystemManager {
|
|
8
|
+
CWD;
|
|
9
|
+
verbose;
|
|
10
|
+
watchers;
|
|
11
|
+
ignorePatterns;
|
|
12
|
+
constructor(cwd = process.cwd(), options = {}) {
|
|
13
|
+
this.CWD = cwd;
|
|
14
|
+
this.verbose = options.verbose || false;
|
|
15
|
+
this.watchers = [];
|
|
16
|
+
this.ignorePatterns = [];
|
|
17
|
+
this.loadIgnorePatterns();
|
|
18
|
+
}
|
|
19
|
+
loadIgnorePatterns() {
|
|
20
|
+
const ignorePath = join(this.CWD, '.cntxignore');
|
|
21
|
+
if (existsSync(ignorePath)) {
|
|
22
|
+
try {
|
|
23
|
+
const content = readFileSync(ignorePath, 'utf8');
|
|
24
|
+
const patterns = content.split('\n')
|
|
25
|
+
.map(line => line.trim())
|
|
26
|
+
.filter(line => line && !line.startsWith('#'));
|
|
27
|
+
this.ignorePatterns = patterns;
|
|
28
|
+
if (this.verbose)
|
|
29
|
+
console.log(`📋 Loaded ${patterns.length} patterns from .cntxignore`);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
console.error('Failed to load .cntxignore:', e.message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// === File Traversal ===
|
|
37
|
+
getAllFiles(dir = this.CWD, files = []) {
|
|
38
|
+
try {
|
|
39
|
+
const items = readdirSync(dir);
|
|
40
|
+
for (const item of items) {
|
|
41
|
+
const fullPath = join(dir, item);
|
|
42
|
+
// Skip if should be ignored
|
|
43
|
+
if (this.shouldIgnoreAnything(item, fullPath)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const stat = statSync(fullPath);
|
|
48
|
+
if (stat.isDirectory()) {
|
|
49
|
+
this.getAllFiles(fullPath, files);
|
|
50
|
+
}
|
|
51
|
+
else if (stat.isFile()) {
|
|
52
|
+
if (!this.shouldIgnoreFile(fullPath)) {
|
|
53
|
+
files.push(fullPath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
// Skip files we can't stat (permission issues, broken symlinks, etc.)
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (this.verbose) {
|
|
65
|
+
console.warn(`Cannot read directory ${dir}: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return files;
|
|
69
|
+
}
|
|
70
|
+
getFileTree() {
|
|
71
|
+
const files = this.getAllFiles();
|
|
72
|
+
return files.map(file => {
|
|
73
|
+
const stats = this.getFileStats(file);
|
|
74
|
+
const relativePath = relative(this.CWD, file);
|
|
75
|
+
return {
|
|
76
|
+
path: relativePath,
|
|
77
|
+
fullPath: file,
|
|
78
|
+
size: stats.size,
|
|
79
|
+
modified: stats.mtime.toISOString(),
|
|
80
|
+
type: this.getFileType(file)
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// === Pattern Matching ===
|
|
85
|
+
matchesPattern(path, pattern) {
|
|
86
|
+
// Convert glob pattern to regex
|
|
87
|
+
let regexPattern = pattern
|
|
88
|
+
.replace(/\./g, '\\.') // Escape dots
|
|
89
|
+
.replace(/\*\*/g, '___GLOBSTAR___') // Temporary replace **
|
|
90
|
+
.replace(/\*/g, '[^/]*') // * matches anything except /
|
|
91
|
+
.replace(/___GLOBSTAR___/g, '.*') // ** matches anything including /
|
|
92
|
+
.replace(/\?/g, '[^/]'); // ? matches single char except /
|
|
93
|
+
// Ensure pattern matches from start or after a directory separator
|
|
94
|
+
if (!regexPattern.startsWith('.*') && !regexPattern.startsWith('[^/]*')) {
|
|
95
|
+
regexPattern = '(^|/)' + regexPattern;
|
|
96
|
+
}
|
|
97
|
+
// Ensure pattern matches to end or before a directory separator
|
|
98
|
+
if (!regexPattern.endsWith('.*') && !regexPattern.endsWith('[^/]*')) {
|
|
99
|
+
regexPattern = regexPattern + '($|/)';
|
|
100
|
+
}
|
|
101
|
+
const regex = new RegExp(regexPattern);
|
|
102
|
+
const relativePath = relative(this.CWD, path);
|
|
103
|
+
return regex.test(relativePath) || regex.test(path);
|
|
104
|
+
}
|
|
105
|
+
shouldIgnoreFile(filePath) {
|
|
106
|
+
const relativePath = relative(this.CWD, filePath);
|
|
107
|
+
return this.ignorePatterns.some(pattern => this.matchesPattern(filePath, pattern) ||
|
|
108
|
+
this.matchesPattern(relativePath, pattern));
|
|
109
|
+
}
|
|
110
|
+
shouldIgnoreAnything(itemName, fullPath) {
|
|
111
|
+
// Hardcoded bad directories and files to always ignore
|
|
112
|
+
const badDirs = [
|
|
113
|
+
'node_modules', '.git', '.svn', '.hg', '.bzr', '_darcs',
|
|
114
|
+
'CVS', '.cvs', 'RCS', 'SCCS', '{arch}', '.arch-ids',
|
|
115
|
+
'.monotone', '_MTN', '.fslckout', '_FOSSIL_',
|
|
116
|
+
'.fos', 'BitKeeper', 'ChangeSet', '.teamcity',
|
|
117
|
+
'.idea', '.vscode', '.vs', '.gradle', '.settings',
|
|
118
|
+
'target', 'build', 'dist', 'out', 'bin', 'obj',
|
|
119
|
+
'.next', '.nuxt', '.vite', '.tmp', '.temp',
|
|
120
|
+
'__pycache__', '.pytest_cache', '.coverage',
|
|
121
|
+
'.nyc_output', 'coverage', 'lcov-report'
|
|
122
|
+
];
|
|
123
|
+
const badExtensions = [
|
|
124
|
+
'.log', '.tmp', '.temp', '.cache', '.pid', '.lock',
|
|
125
|
+
'.swp', '.swo', '.DS_Store', 'Thumbs.db', '.env',
|
|
126
|
+
'.min.js', '.min.css', '.map', '.pyc', '.pyo',
|
|
127
|
+
'.class', '.jar', '.exe', '.dll', '.so', '.dylib',
|
|
128
|
+
'.o', '.a', '.obj', '.lib', '.pdb'
|
|
129
|
+
];
|
|
130
|
+
const badFiles = [
|
|
131
|
+
'.gitignore', '.gitkeep', '.gitattributes',
|
|
132
|
+
'.eslintcache', '.prettierignore',
|
|
133
|
+
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
|
|
134
|
+
'npm-debug.log', 'yarn-debug.log', 'yarn-error.log'
|
|
135
|
+
];
|
|
136
|
+
// Check bad directories
|
|
137
|
+
if (badDirs.includes(itemName)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
// Check bad extensions
|
|
141
|
+
const ext = extname(itemName);
|
|
142
|
+
if (badExtensions.includes(ext)) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
// Check bad files
|
|
146
|
+
if (badFiles.includes(itemName)) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
// Check if it's a hidden file/directory (starts with .)
|
|
150
|
+
if (itemName.startsWith('.') && itemName !== '.cntx') {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
// Check ignore patterns if loaded
|
|
154
|
+
if (this.ignorePatterns.length > 0) {
|
|
155
|
+
const relativePath = relative(this.CWD, fullPath);
|
|
156
|
+
if (this.ignorePatterns.some(pattern => this.matchesPattern(fullPath, pattern) ||
|
|
157
|
+
this.matchesPattern(relativePath, pattern))) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
// === File Metadata ===
|
|
164
|
+
getFileStats(filePath) {
|
|
165
|
+
try {
|
|
166
|
+
return statSync(filePath);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
// Return a mock stats object for missing files
|
|
170
|
+
return {
|
|
171
|
+
size: 0,
|
|
172
|
+
mtime: new Date(0),
|
|
173
|
+
ctime: new Date(0),
|
|
174
|
+
isDirectory: () => false,
|
|
175
|
+
isFile: () => false
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
getFileType(filePath) {
|
|
180
|
+
const ext = extname(filePath).toLowerCase();
|
|
181
|
+
const fileName = basename(filePath).toLowerCase();
|
|
182
|
+
// Programming languages
|
|
183
|
+
if (ext.match(/\.(js|jsx|mjs|cjs)$/))
|
|
184
|
+
return 'javascript';
|
|
185
|
+
if (ext.match(/\.(ts|tsx)$/))
|
|
186
|
+
return 'typescript';
|
|
187
|
+
if (ext.match(/\.(py|pyw)$/))
|
|
188
|
+
return 'python';
|
|
189
|
+
if (ext.match(/\.(java|class)$/))
|
|
190
|
+
return 'java';
|
|
191
|
+
if (ext.match(/\.(c|h)$/))
|
|
192
|
+
return 'c';
|
|
193
|
+
if (ext.match(/\.(cpp|cxx|cc|hpp|hxx)$/))
|
|
194
|
+
return 'cpp';
|
|
195
|
+
if (ext.match(/\.(cs)$/))
|
|
196
|
+
return 'csharp';
|
|
197
|
+
if (ext.match(/\.(go)$/))
|
|
198
|
+
return 'go';
|
|
199
|
+
if (ext.match(/\.(rs)$/))
|
|
200
|
+
return 'rust';
|
|
201
|
+
if (ext.match(/\.(php)$/))
|
|
202
|
+
return 'php';
|
|
203
|
+
if (ext.match(/\.(rb)$/))
|
|
204
|
+
return 'ruby';
|
|
205
|
+
// Web technologies
|
|
206
|
+
if (ext.match(/\.(html|htm)$/))
|
|
207
|
+
return 'html';
|
|
208
|
+
if (ext.match(/\.(css|scss|sass|less|styl)$/))
|
|
209
|
+
return 'stylesheet';
|
|
210
|
+
if (ext.match(/\.(vue)$/))
|
|
211
|
+
return 'vue';
|
|
212
|
+
// Data formats
|
|
213
|
+
if (ext.match(/\.(json)$/))
|
|
214
|
+
return 'json';
|
|
215
|
+
if (ext.match(/\.(xml)$/))
|
|
216
|
+
return 'xml';
|
|
217
|
+
if (ext.match(/\.(yaml|yml)$/))
|
|
218
|
+
return 'yaml';
|
|
219
|
+
if (ext.match(/\.(toml)$/))
|
|
220
|
+
return 'toml';
|
|
221
|
+
if (ext.match(/\.(ini)$/))
|
|
222
|
+
return 'ini';
|
|
223
|
+
if (ext.match(/\.(csv)$/))
|
|
224
|
+
return 'csv';
|
|
225
|
+
// Documentation
|
|
226
|
+
if (ext.match(/\.(md|markdown)$/))
|
|
227
|
+
return 'markdown';
|
|
228
|
+
if (ext.match(/\.(txt)$/))
|
|
229
|
+
return 'text';
|
|
230
|
+
if (ext.match(/\.(rst)$/))
|
|
231
|
+
return 'restructuredtext';
|
|
232
|
+
// Media
|
|
233
|
+
if (ext.match(/\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/))
|
|
234
|
+
return 'image';
|
|
235
|
+
if (ext.match(/\.(mp4|avi|mov|wmv|flv|webm)$/))
|
|
236
|
+
return 'video';
|
|
237
|
+
if (ext.match(/\.(mp3|wav|flac|aac|ogg)$/))
|
|
238
|
+
return 'audio';
|
|
239
|
+
// Archives
|
|
240
|
+
if (ext.match(/\.(zip|tar|gz|bz2|xz|7z|rar)$/))
|
|
241
|
+
return 'archive';
|
|
242
|
+
// Configuration
|
|
243
|
+
if (fileName.includes('config') || fileName.includes('setup'))
|
|
244
|
+
return 'configuration';
|
|
245
|
+
return 'unknown';
|
|
246
|
+
}
|
|
247
|
+
// === File Watching ===
|
|
248
|
+
startWatching(onFileChange) {
|
|
249
|
+
try {
|
|
250
|
+
// Watch the current working directory recursively
|
|
251
|
+
const watcher = watch(this.CWD, { recursive: true }, (eventType, filename) => {
|
|
252
|
+
if (filename && !this.shouldIgnoreAnything(basename(filename), join(this.CWD, filename))) {
|
|
253
|
+
onFileChange?.(eventType, filename);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
this.watchers.push(watcher);
|
|
257
|
+
if (this.verbose) {
|
|
258
|
+
console.log('📁 File watcher started');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (this.verbose) {
|
|
263
|
+
console.error('Failed to start file watcher:', error.message);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
stopWatching() {
|
|
268
|
+
this.watchers.forEach(watcher => {
|
|
269
|
+
try {
|
|
270
|
+
watcher.close();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
if (this.verbose) {
|
|
274
|
+
console.error('Failed to close watcher:', error.message);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
this.watchers = [];
|
|
279
|
+
if (this.verbose) {
|
|
280
|
+
console.log('📁 File watchers stopped');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// === Utilities ===
|
|
284
|
+
setIgnorePatterns(patterns) {
|
|
285
|
+
this.ignorePatterns = patterns;
|
|
286
|
+
}
|
|
287
|
+
relativePath(filePath) {
|
|
288
|
+
return relative(this.CWD, filePath);
|
|
289
|
+
}
|
|
290
|
+
fullPath(relativePath) {
|
|
291
|
+
return join(this.CWD, relativePath);
|
|
292
|
+
}
|
|
293
|
+
isValidPath(filePath) {
|
|
294
|
+
try {
|
|
295
|
+
return existsSync(filePath);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// === Cleanup ===
|
|
302
|
+
destroy() {
|
|
303
|
+
this.stopWatching();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function-Level Semantic Chunker
|
|
3
|
+
* Extracts individual functions/methods/components as discrete chunks
|
|
4
|
+
* with intelligent context inclusion
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, existsSync } from 'fs';
|
|
7
|
+
import { extname, join } from 'path';
|
|
8
|
+
import { glob } from 'glob';
|
|
9
|
+
export default class FunctionLevelChunker {
|
|
10
|
+
options;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.options = {
|
|
13
|
+
minChunkSize: 100,
|
|
14
|
+
maxChunkSize: 10000,
|
|
15
|
+
includeImports: true,
|
|
16
|
+
includeTypes: true,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Main entry point - extract function chunks from project
|
|
22
|
+
*/
|
|
23
|
+
async extractFunctionChunks(projectPath, patterns = ['**/*.{js,jsx,ts,tsx,mjs}']) {
|
|
24
|
+
console.log('🔍 Starting function-level semantic chunking...');
|
|
25
|
+
const files = await this.findFiles(projectPath, patterns);
|
|
26
|
+
console.log(`📁 Found ${files.length} files to scan for functions`);
|
|
27
|
+
const allFunctions = [];
|
|
28
|
+
for (const filePath of files) {
|
|
29
|
+
try {
|
|
30
|
+
const fileFunctions = this.extractFunctionsFromFile(filePath, projectPath);
|
|
31
|
+
allFunctions.push(...fileFunctions);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.warn(`Failed to extract from ${filePath}: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
console.log(`📦 Found ${allFunctions.length} functions across project`);
|
|
38
|
+
const chunks = this.createFunctionChunks(allFunctions);
|
|
39
|
+
console.log(`🧩 Created ${chunks.length} semantic function chunks`);
|
|
40
|
+
return chunks;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Find files matching patterns
|
|
44
|
+
*/
|
|
45
|
+
async findFiles(projectPath, patterns) {
|
|
46
|
+
const files = [];
|
|
47
|
+
for (const pattern of patterns) {
|
|
48
|
+
const matches = await glob(pattern, {
|
|
49
|
+
cwd: projectPath,
|
|
50
|
+
ignore: ['node_modules/**', 'dist/**', '.git/**', '*.test.*', '*.spec.*']
|
|
51
|
+
});
|
|
52
|
+
files.push(...matches);
|
|
53
|
+
}
|
|
54
|
+
return [...new Set(files)];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extract functions from a single file
|
|
58
|
+
*/
|
|
59
|
+
extractFunctionsFromFile(relativePath, projectPath) {
|
|
60
|
+
const fullPath = join(projectPath, relativePath);
|
|
61
|
+
if (!existsSync(fullPath))
|
|
62
|
+
return [];
|
|
63
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
64
|
+
const lines = content.split('\n');
|
|
65
|
+
const ext = extname(relativePath);
|
|
66
|
+
// For now, use regex-based extraction as primary
|
|
67
|
+
// In a full implementation, we'd use treesitter here
|
|
68
|
+
return this.extractWithRegex(content, lines, relativePath);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Simple regex-based extraction for functions and methods
|
|
72
|
+
*/
|
|
73
|
+
extractWithRegex(content, lines, filePath) {
|
|
74
|
+
const functions = [];
|
|
75
|
+
// Patterns for common function declarations
|
|
76
|
+
const patterns = [
|
|
77
|
+
// function name() {
|
|
78
|
+
/function\s+([a-zA-Z0-9_]+)\s*\(/g,
|
|
79
|
+
// const name = () => {
|
|
80
|
+
/(?:const|let|var)\s+([a-zA-Z0-9_]+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g,
|
|
81
|
+
// name(params) { (inside class or object)
|
|
82
|
+
/^\s*([a-zA-Z0-9_]+)\s*\([^)]*\)\s*\{/gm
|
|
83
|
+
];
|
|
84
|
+
for (const pattern of patterns) {
|
|
85
|
+
let match;
|
|
86
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
87
|
+
const name = match[1];
|
|
88
|
+
const startIndex = match.index;
|
|
89
|
+
// Find line number
|
|
90
|
+
const beforeMatch = content.substring(0, startIndex);
|
|
91
|
+
const startLine = beforeMatch.split('\n').length;
|
|
92
|
+
// Extract function body (simple brace matching)
|
|
93
|
+
const body = this.extractFunctionBody(content, startIndex, lines, startLine);
|
|
94
|
+
if (body && body.length > this.options.minChunkSize) {
|
|
95
|
+
functions.push({
|
|
96
|
+
name,
|
|
97
|
+
code: body,
|
|
98
|
+
filePath,
|
|
99
|
+
startLine,
|
|
100
|
+
type: 'function'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return functions;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Extract function body using brace matching
|
|
109
|
+
*/
|
|
110
|
+
extractFunctionBody(content, startIndex, lines, startLine) {
|
|
111
|
+
const openingBraceIndex = content.indexOf('{', startIndex);
|
|
112
|
+
if (openingBraceIndex === -1)
|
|
113
|
+
return null;
|
|
114
|
+
let braceCount = 1;
|
|
115
|
+
let i = openingBraceIndex + 1;
|
|
116
|
+
while (braceCount > 0 && i < content.length) {
|
|
117
|
+
if (content[i] === '{')
|
|
118
|
+
braceCount++;
|
|
119
|
+
if (content[i] === '}')
|
|
120
|
+
braceCount--;
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
if (braceCount === 0) {
|
|
124
|
+
return content.substring(startIndex, i);
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Create semantic chunks from extracted functions
|
|
130
|
+
*/
|
|
131
|
+
createFunctionChunks(functions) {
|
|
132
|
+
return functions.map(func => ({
|
|
133
|
+
id: `${func.filePath}:${func.name}:${func.startLine}`,
|
|
134
|
+
name: func.name,
|
|
135
|
+
filePath: func.filePath,
|
|
136
|
+
type: 'function',
|
|
137
|
+
subtype: func.type || 'unknown',
|
|
138
|
+
code: func.code,
|
|
139
|
+
startLine: func.startLine,
|
|
140
|
+
purpose: 'Implementation logic', // Default purpose
|
|
141
|
+
complexity: { score: 0, level: 'low' } // Placeholder
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
}
|