minovative-mind-cli 2.13.5 → 2.14.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.
- package/README.md +102 -39
- package/dist/services/agent/slashCommands.js +47 -7
- package/dist/services/agent/syntaxAgent.js +13 -0
- package/dist/services/agent-tools.d.ts +1 -1
- package/dist/services/agent-tools.js +2 -2
- package/dist/services/agent.js +119 -7
- package/dist/services/ai.d.ts +19 -0
- package/dist/services/ai.js +96 -5
- package/dist/services/contextAgent.js +23 -0
- package/dist/services/investigationComplexity.js +1 -1
- package/dist/services/mentionEngine.d.ts +385 -0
- package/dist/services/mentionEngine.js +1395 -0
- package/dist/services/orchestration/investigationAgent.js +4 -1
- package/dist/services/orchestration/messageBus.d.ts +26 -3
- package/dist/services/orchestration/messageBus.js +204 -14
- package/dist/services/orchestration/orchestrator.js +4 -0
- package/dist/services/orchestration/scopedTools.js +16 -2
- package/dist/services/orchestration/subAgent.js +14 -2
- package/dist/services/proxyClient.d.ts +38 -2
- package/dist/services/proxyClient.js +42 -24
- package/dist/utils/config.d.ts +75 -0
- package/dist/utils/config.js +93 -0
- package/dist/utils/contextPrompts.d.ts +28 -4
- package/dist/utils/contextPrompts.js +70 -1
- package/dist/utils/historyPrompt.d.ts +166 -7
- package/dist/utils/historyPrompt.js +775 -30
- package/dist/utils/symbolExtractor.d.ts +111 -8
- package/dist/utils/symbolExtractor.js +616 -64
- package/dist/utils/systemPrompts.d.ts +3 -3
- package/dist/utils/systemPrompts.js +5 -3
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Estimates token consumption for a given string using a standard ~3.8 characters per token heuristic.
|
|
4
|
+
*
|
|
5
|
+
* @param text - The input string to estimate
|
|
6
|
+
* @returns Estimated token count
|
|
7
|
+
*/
|
|
8
|
+
export function estimateTokens(text) {
|
|
9
|
+
if (!text)
|
|
10
|
+
return 0;
|
|
11
|
+
return Math.ceil(text.length / 3.8);
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* Calculates the leading indentation whitespace count of a source code line.
|
|
4
15
|
*
|
|
@@ -20,13 +31,37 @@ function isCommentOrEmpty(line, ext) {
|
|
|
20
31
|
const trimmed = line.trim();
|
|
21
32
|
if (!trimmed)
|
|
22
33
|
return true;
|
|
23
|
-
if (ext === '.py')
|
|
34
|
+
if (ext === '.py' || ext === '.pyi')
|
|
24
35
|
return trimmed.startsWith('#');
|
|
25
36
|
return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*');
|
|
26
37
|
}
|
|
27
38
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
39
|
+
* Parses a target symbol identifier into potential parent and member components.
|
|
40
|
+
* Handles patterns such as:
|
|
41
|
+
* - `UserService.getUser` -> `{ parentName: 'UserService', memberName: 'getUser' }`
|
|
42
|
+
* - `UserService#getUser` -> `{ parentName: 'UserService', memberName: 'getUser' }`
|
|
43
|
+
* - `UserService::getUser` -> `{ parentName: 'UserService', memberName: 'getUser' }`
|
|
44
|
+
* - `getUser` -> `{ parentName: null, memberName: 'getUser' }`
|
|
45
|
+
*
|
|
46
|
+
* @param rawTarget - The raw symbol string provided by the caller
|
|
47
|
+
* @returns Object with optional parentName and memberName
|
|
48
|
+
*/
|
|
49
|
+
export function parseTargetSymbol(rawTarget) {
|
|
50
|
+
const trimmed = rawTarget.trim();
|
|
51
|
+
const sepMatch = trimmed.match(/^([A-Za-z0-9_$]+)(?:\.|#|::)([A-Za-z0-9_$]+)$/);
|
|
52
|
+
if (sepMatch) {
|
|
53
|
+
return {
|
|
54
|
+
parentName: sepMatch[1],
|
|
55
|
+
memberName: sepMatch[2],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
parentName: null,
|
|
60
|
+
memberName: trimmed,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Builds a regular expression for matching top-level symbol declarations across supported language families.
|
|
30
65
|
*
|
|
31
66
|
* @param symbolName - The name of the symbol to match
|
|
32
67
|
* @param ext - The file extension determining language syntax rules
|
|
@@ -34,16 +69,110 @@ function isCommentOrEmpty(line, ext) {
|
|
|
34
69
|
*/
|
|
35
70
|
function buildDeclarationRegex(symbolName, ext) {
|
|
36
71
|
const s = symbolName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
37
|
-
if (ext === '.py') {
|
|
72
|
+
if (ext === '.py' || ext === '.pyi') {
|
|
38
73
|
return new RegExp(`^\\s*(?:async\\s+)?(?:def|class)\\s+${s}\\b|^\\s*${s}\\s*[:=]`);
|
|
39
74
|
}
|
|
40
75
|
if (ext === '.go') {
|
|
41
76
|
return new RegExp(`^\\s*(?:func(?:\\s+\\([^)]+\\))?\\s+${s}\\b|type\\s+${s}\\b|var\\s+${s}\\b|const\\s+${s}\\b)`);
|
|
42
77
|
}
|
|
43
78
|
if (ext === '.rs') {
|
|
44
|
-
return new RegExp(`^\\s*(?:(?:pub
|
|
79
|
+
return new RegExp(`^\\s*(?:(?:pub(?:\\([^)]+\\))?\\s+)?(?:async\\s+|const\\s+|unsafe\\s+|extern\\s+)?fn\\s+${s}\\b|(?:pub(?:\\([^)]+\\))?\\s+)?struct\\s+${s}\\b|(?:pub(?:\\([^)]+\\))?\\s+)?enum\\s+${s}\\b|(?:pub(?:\\([^)]+\\))?\\s+)?trait\\s+${s}\\b|(?:pub(?:\\([^)]+\\))?\\s+)?impl(?:\\s+.*)?\\s+(?:for\\s+)?${s}\\b|(?:pub(?:\\([^)]+\\))?\\s+)?type\\s+${s}\\b|(?:pub(?:\\([^)]+\\))?\\s+)?const\\s+${s}\\b)`);
|
|
45
80
|
}
|
|
46
|
-
return new RegExp(`^\\s*(?:(?:export\\s+|default\\s+|async\\s+|abstract\\s+|static\\s+|public\\s+|private\\s+|protected\\s+|readonly\\s+)*(?:class|function|const|let|var|type|interface|enum)\\s+(?:[\\w<>]+\\s+)?${s}\\b|^\\s*${s}\\s*[:=]|^\\s*(?:(?:public|private|protected|static|abstract|override|async|\\s+)*)${s}\\s*(?:<[^>]+>)?\\s*\\()`);
|
|
81
|
+
return new RegExp(`^\\s*(?:(?:export\\s+|default\\s+|async\\s+|abstract\\s+|static\\s+|public\\s+|private\\s+|protected\\s+|readonly\\s+|declare\\s+)*(?:class|function|const|let|var|type|interface|enum)\\s+(?:[\\w<>]+\\s+)?${s}\\b|^\\s*${s}\\s*[:=]|^\\s*(?:(?:public|private|protected|static|abstract|override|async|readonly|\\s+)*)${s}\\s*(?:<[^>]+>)?\\s*\\()`);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Builds a regular expression for matching nested class/struct/impl member methods and fields.
|
|
85
|
+
*
|
|
86
|
+
* @param memberName - The name of the member method or property to match
|
|
87
|
+
* @param ext - The file extension
|
|
88
|
+
* @returns A RegExp matching the member definition within an enclosing body
|
|
89
|
+
*/
|
|
90
|
+
function buildMemberRegex(memberName, ext) {
|
|
91
|
+
const m = memberName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
92
|
+
if (ext === '.py' || ext === '.pyi') {
|
|
93
|
+
return new RegExp(`^\\s*(?:async\\s+)?def\\s+${m}\\b|^\\s*${m}\\s*[:=]`);
|
|
94
|
+
}
|
|
95
|
+
if (ext === '.rs') {
|
|
96
|
+
return new RegExp(`^\\s*(?:(?:pub(?:\\([^)]+\\))?\\s+)?(?:async\\s+|const\\s+|unsafe\\s+|extern\\s+)?fn\\s+${m}\\b|(?:pub(?:\\([^)]+\\))?\\s+)?(?:type|const)\\s+${m}\\b)`);
|
|
97
|
+
}
|
|
98
|
+
if (ext === '.go') {
|
|
99
|
+
return new RegExp(`^\\s*func\\s+\\([^)]+\\)\\s+${m}\\b|^\\s*${m}\\s+`);
|
|
100
|
+
}
|
|
101
|
+
return new RegExp(`^\\s*(?:(?:public|private|protected|static|abstract|override|async|readonly|get|set|\\s+)*)${m}\\s*(?:<[^>]+>)?\\s*\\(|^\\s*(?:public|private|protected|static|readonly|\\s+)*${m}\\s*[:=;]`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Infers the {@link SymbolKind} from a line and extension.
|
|
105
|
+
*
|
|
106
|
+
* @param line - The line declaring the symbol
|
|
107
|
+
* @param ext - The file extension
|
|
108
|
+
* @param isNested - Whether the symbol is inside a parent class/struct/impl
|
|
109
|
+
* @returns The classified SymbolKind
|
|
110
|
+
*/
|
|
111
|
+
function inferSymbolKind(line, ext, isNested) {
|
|
112
|
+
const trimmed = line.trim();
|
|
113
|
+
if (ext === '.py' || ext === '.pyi') {
|
|
114
|
+
if (trimmed.startsWith('class '))
|
|
115
|
+
return 'class';
|
|
116
|
+
if (trimmed.includes('def '))
|
|
117
|
+
return isNested ? 'method' : 'function';
|
|
118
|
+
return 'variable';
|
|
119
|
+
}
|
|
120
|
+
if (ext === '.rs') {
|
|
121
|
+
if (trimmed.includes('struct '))
|
|
122
|
+
return 'struct';
|
|
123
|
+
if (trimmed.includes('enum '))
|
|
124
|
+
return 'enum';
|
|
125
|
+
if (trimmed.includes('trait '))
|
|
126
|
+
return 'trait';
|
|
127
|
+
if (trimmed.includes('impl '))
|
|
128
|
+
return 'impl';
|
|
129
|
+
if (trimmed.includes('type '))
|
|
130
|
+
return 'type';
|
|
131
|
+
if (trimmed.includes('const ') || trimmed.includes('static '))
|
|
132
|
+
return 'constant';
|
|
133
|
+
if (trimmed.includes('fn '))
|
|
134
|
+
return isNested ? 'method' : 'function';
|
|
135
|
+
return 'other';
|
|
136
|
+
}
|
|
137
|
+
if (ext === '.go') {
|
|
138
|
+
if (trimmed.includes('struct'))
|
|
139
|
+
return 'struct';
|
|
140
|
+
if (trimmed.includes('interface'))
|
|
141
|
+
return 'interface';
|
|
142
|
+
if (trimmed.startsWith('type '))
|
|
143
|
+
return 'type';
|
|
144
|
+
if (trimmed.startsWith('const '))
|
|
145
|
+
return 'constant';
|
|
146
|
+
if (trimmed.startsWith('var '))
|
|
147
|
+
return 'variable';
|
|
148
|
+
if (trimmed.startsWith('func ('))
|
|
149
|
+
return 'method';
|
|
150
|
+
if (trimmed.startsWith('func '))
|
|
151
|
+
return 'function';
|
|
152
|
+
return 'other';
|
|
153
|
+
}
|
|
154
|
+
// TypeScript / JavaScript
|
|
155
|
+
if (/\binterface\b/.test(trimmed))
|
|
156
|
+
return 'interface';
|
|
157
|
+
if (/\btype\b/.test(trimmed))
|
|
158
|
+
return 'type';
|
|
159
|
+
if (/\benum\b/.test(trimmed))
|
|
160
|
+
return 'enum';
|
|
161
|
+
if (/\bclass\b/.test(trimmed))
|
|
162
|
+
return 'class';
|
|
163
|
+
if (/\bconstructor\b/.test(trimmed))
|
|
164
|
+
return 'constructor';
|
|
165
|
+
if (/\bfunction\b/.test(trimmed) || (trimmed.includes('=>') && !isNested))
|
|
166
|
+
return 'function';
|
|
167
|
+
if (isNested && (trimmed.includes('(') || /\b(get|set)\b/.test(trimmed)))
|
|
168
|
+
return 'method';
|
|
169
|
+
if (/\b(const|readonly)\b/.test(trimmed))
|
|
170
|
+
return 'constant';
|
|
171
|
+
if (/\b(let|var)\b/.test(trimmed))
|
|
172
|
+
return 'variable';
|
|
173
|
+
if (isNested)
|
|
174
|
+
return 'property';
|
|
175
|
+
return 'other';
|
|
47
176
|
}
|
|
48
177
|
/**
|
|
49
178
|
* Scans backwards from a declaration index to collect preceding docstrings, JSDoc blocks, decorators, and line comments.
|
|
@@ -58,9 +187,9 @@ function findPrecedingContext(lines, declarationIndex, ext) {
|
|
|
58
187
|
let insideJsDoc = false;
|
|
59
188
|
while (start > 0) {
|
|
60
189
|
const prevLine = lines[start - 1].trim();
|
|
61
|
-
const isJsDocEnd = ['.ts', '.js', '.tsx', '.jsx'].includes(ext) && prevLine === '*/';
|
|
62
|
-
const isJsDocLine = ['.ts', '.js', '.tsx', '.jsx'].includes(ext) && prevLine.startsWith('*');
|
|
63
|
-
const isJsDocStart = ['.ts', '.js', '.tsx', '.jsx'].includes(ext) && prevLine.startsWith('/**');
|
|
190
|
+
const isJsDocEnd = ['.ts', '.js', '.tsx', '.jsx', '.mts', '.cts', '.mjs', '.cjs'].includes(ext) && prevLine === '*/';
|
|
191
|
+
const isJsDocLine = ['.ts', '.js', '.tsx', '.jsx', '.mts', '.cts', '.mjs', '.cjs'].includes(ext) && prevLine.startsWith('*');
|
|
192
|
+
const isJsDocStart = ['.ts', '.js', '.tsx', '.jsx', '.mts', '.cts', '.mjs', '.cjs'].includes(ext) && prevLine.startsWith('/**');
|
|
64
193
|
if (isJsDocEnd)
|
|
65
194
|
insideJsDoc = true;
|
|
66
195
|
if (!prevLine) {
|
|
@@ -73,9 +202,10 @@ function findPrecedingContext(lines, declarationIndex, ext) {
|
|
|
73
202
|
}
|
|
74
203
|
}
|
|
75
204
|
const isDecorator = prevLine.startsWith('@');
|
|
76
|
-
const isPythonComment = ext === '.py' && prevLine.startsWith('#');
|
|
205
|
+
const isPythonComment = (ext === '.py' || ext === '.pyi') && prevLine.startsWith('#');
|
|
77
206
|
const isLineComment = prevLine.startsWith('//');
|
|
78
|
-
|
|
207
|
+
const isRustDocOrAttr = (ext === '.rs') && (prevLine.startsWith('///') || prevLine.startsWith('//!') || prevLine.startsWith('#['));
|
|
208
|
+
if (isDecorator || isPythonComment || isLineComment || isJsDocEnd || isJsDocLine || isJsDocStart || isRustDocOrAttr) {
|
|
79
209
|
start--;
|
|
80
210
|
if (isJsDocStart) {
|
|
81
211
|
insideJsDoc = false;
|
|
@@ -112,7 +242,7 @@ function scanLineValidity(line, state) {
|
|
|
112
242
|
if (!state.inMultiLineComment && !state.inMultiLineString) {
|
|
113
243
|
if (line[j] === '/' && line[j + 1] === '/')
|
|
114
244
|
break;
|
|
115
|
-
if (state.ext === '.py' && line[j] === '#')
|
|
245
|
+
if ((state.ext === '.py' || state.ext === '.pyi') && line[j] === '#')
|
|
116
246
|
break;
|
|
117
247
|
if (line[j] === '/' && line[j + 1] === '*') {
|
|
118
248
|
state.inMultiLineComment = true;
|
|
@@ -208,7 +338,7 @@ function processLineChars(line, state) {
|
|
|
208
338
|
*/
|
|
209
339
|
function findBlockEnd(lines, i, ext) {
|
|
210
340
|
let endIndex = i;
|
|
211
|
-
if (ext === '.py') {
|
|
341
|
+
if (ext === '.py' || ext === '.pyi') {
|
|
212
342
|
const baseIndentation = getIndentation(lines[i]);
|
|
213
343
|
for (let j = i + 1; j < lines.length; j++) {
|
|
214
344
|
if (isCommentOrEmpty(lines[j], ext)) {
|
|
@@ -250,26 +380,36 @@ function addLinesToKeep(linesToKeep, start, end) {
|
|
|
250
380
|
linesToKeep.add(k);
|
|
251
381
|
}
|
|
252
382
|
/**
|
|
253
|
-
*
|
|
383
|
+
* Extracts clean signature representation from lines.
|
|
254
384
|
*
|
|
255
|
-
* @param
|
|
256
|
-
* @param
|
|
257
|
-
* @
|
|
385
|
+
* @param lines - File lines
|
|
386
|
+
* @param startLine - Start index of declaration
|
|
387
|
+
* @param endLine - End index of block
|
|
388
|
+
* @returns Cleaned signature string
|
|
258
389
|
*/
|
|
259
|
-
function
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
390
|
+
function extractCleanSignature(lines, startLine, endLine) {
|
|
391
|
+
const sigLines = [];
|
|
392
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
393
|
+
const line = lines[i];
|
|
394
|
+
sigLines.push(line);
|
|
395
|
+
if (line.includes('{') || line.trim().endsWith(':') || line.trim().endsWith(';'))
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
const rawSig = sigLines.join('\n').trim();
|
|
399
|
+
const braceIdx = rawSig.indexOf('{');
|
|
400
|
+
if (braceIdx !== -1) {
|
|
401
|
+
return rawSig.substring(0, braceIdx).trim();
|
|
263
402
|
}
|
|
264
|
-
return
|
|
403
|
+
return rawSig;
|
|
265
404
|
}
|
|
266
405
|
/**
|
|
267
|
-
* Extracts specified target symbols from source file content and returns their line number ranges and
|
|
406
|
+
* Extracts specified target symbols from source file content and returns their line number ranges, kind, and signatures.
|
|
407
|
+
* Supports granular dotted member extraction (e.g. `UserService.getUser` or `User#new`).
|
|
268
408
|
*
|
|
269
409
|
* @param content - The full raw string content of the source file
|
|
270
410
|
* @param filePath - The file path (used to determine language syntax rules via file extension)
|
|
271
|
-
* @param targetElements - An array of symbol names to extract
|
|
272
|
-
* @returns Array of ExtractedSymbol objects containing symbol name,
|
|
411
|
+
* @param targetElements - An array of symbol names or dotted member targets to extract
|
|
412
|
+
* @returns Array of ExtractedSymbol objects containing symbol name, line ranges, and metadata
|
|
273
413
|
*/
|
|
274
414
|
export function extractSymbolMetadata(content, filePath, targetElements) {
|
|
275
415
|
if (!targetElements || targetElements.length === 0)
|
|
@@ -285,7 +425,16 @@ export function extractSymbolMetadata(content, filePath, targetElements) {
|
|
|
285
425
|
validLines[i] = false;
|
|
286
426
|
}
|
|
287
427
|
}
|
|
288
|
-
const
|
|
428
|
+
const symbolTargets = targetElements.map((target) => {
|
|
429
|
+
const { parentName, memberName } = parseTargetSymbol(target);
|
|
430
|
+
return {
|
|
431
|
+
target,
|
|
432
|
+
parentName,
|
|
433
|
+
memberName,
|
|
434
|
+
regex: buildDeclarationRegex(parentName || memberName, ext),
|
|
435
|
+
memberRegex: buildMemberRegex(memberName, ext),
|
|
436
|
+
};
|
|
437
|
+
});
|
|
289
438
|
const symbols = [];
|
|
290
439
|
let i = 0;
|
|
291
440
|
while (i < lines.length) {
|
|
@@ -293,67 +442,470 @@ export function extractSymbolMetadata(content, filePath, targetElements) {
|
|
|
293
442
|
i++;
|
|
294
443
|
continue;
|
|
295
444
|
}
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
445
|
+
const line = lines[i];
|
|
446
|
+
for (const symTarget of symbolTargets) {
|
|
447
|
+
// 1. If target specified a parent (e.g. Class.method)
|
|
448
|
+
if (symTarget.parentName) {
|
|
449
|
+
if (symTarget.regex.test(line)) {
|
|
450
|
+
const parentStartIndex = findPrecedingContext(lines, i, ext);
|
|
451
|
+
const parentEndIndex = findBlockEnd(lines, i, ext);
|
|
452
|
+
// Scan inside the parent body for the member
|
|
453
|
+
for (let m = i + 1; m <= parentEndIndex; m++) {
|
|
454
|
+
if (!validLines[m])
|
|
455
|
+
continue;
|
|
456
|
+
if (symTarget.memberRegex.test(lines[m])) {
|
|
457
|
+
const memberStartIndex = findPrecedingContext(lines, m, ext);
|
|
458
|
+
const memberEndIndex = findBlockEnd(lines, m, ext);
|
|
459
|
+
const sig = extractCleanSignature(lines, memberStartIndex, memberEndIndex);
|
|
460
|
+
symbols.push({
|
|
461
|
+
symbol: symTarget.target,
|
|
462
|
+
parentSymbol: symTarget.parentName,
|
|
463
|
+
startLine: memberStartIndex,
|
|
464
|
+
endLine: memberEndIndex,
|
|
465
|
+
kind: inferSymbolKind(lines[m], ext, true),
|
|
466
|
+
signature: sig,
|
|
467
|
+
});
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
// 2. Direct top-level match or standalone member match
|
|
475
|
+
if (symTarget.regex.test(line)) {
|
|
476
|
+
const startIndex = findPrecedingContext(lines, i, ext);
|
|
477
|
+
const endIndex = findBlockEnd(lines, i, ext);
|
|
478
|
+
const sig = extractCleanSignature(lines, startIndex, endIndex);
|
|
479
|
+
symbols.push({
|
|
480
|
+
symbol: symTarget.target,
|
|
481
|
+
startLine: startIndex,
|
|
482
|
+
endLine: endIndex,
|
|
483
|
+
kind: inferSymbolKind(line, ext, false),
|
|
484
|
+
signature: sig,
|
|
485
|
+
});
|
|
486
|
+
i = endIndex;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
306
490
|
}
|
|
307
|
-
|
|
491
|
+
i++;
|
|
492
|
+
}
|
|
493
|
+
// Deduplicate matched ranges
|
|
494
|
+
const uniqueSymbols = [];
|
|
495
|
+
const seenRanges = new Set();
|
|
496
|
+
for (const s of symbols) {
|
|
497
|
+
const key = `${s.symbol}:${s.startLine}:${s.endLine}`;
|
|
498
|
+
if (!seenRanges.has(key)) {
|
|
499
|
+
seenRanges.add(key);
|
|
500
|
+
uniqueSymbols.push(s);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return uniqueSymbols;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Scans a source file and extracts a comprehensive index of all declared symbols (classes, functions,
|
|
507
|
+
* methods, interfaces, structs, enums, types, etc.) along with their kind, line boundaries, and signatures.
|
|
508
|
+
*
|
|
509
|
+
* @param content - Source file content
|
|
510
|
+
* @param filePath - File path used to infer language and syntax rules
|
|
511
|
+
* @returns Array of all discovered ExtractedSymbol objects
|
|
512
|
+
*/
|
|
513
|
+
export function extractSymbolIndex(content, filePath) {
|
|
514
|
+
if (!content || !content.trim())
|
|
515
|
+
return [];
|
|
516
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
517
|
+
const lines = content.split('\n');
|
|
518
|
+
const symbols = [];
|
|
519
|
+
let i = 0;
|
|
520
|
+
while (i < lines.length) {
|
|
521
|
+
const line = lines[i];
|
|
522
|
+
const trimmed = line.trim();
|
|
523
|
+
if (!trimmed || isCommentOrEmpty(trimmed, ext)) {
|
|
308
524
|
i++;
|
|
525
|
+
continue;
|
|
309
526
|
}
|
|
527
|
+
// Check TS/JS declarations
|
|
528
|
+
if (['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'].includes(ext)) {
|
|
529
|
+
const classMatch = trimmed.match(/^(?:export\s+|default\s+|abstract\s+|declare\s+)*class\s+([A-Za-z0-9_$]+)/);
|
|
530
|
+
const ifaceMatch = trimmed.match(/^(?:export\s+|declare\s+|default\s+)*interface\s+([A-Za-z0-9_$]+)/);
|
|
531
|
+
const typeMatch = trimmed.match(/^(?:export\s+|declare\s+)*type\s+([A-Za-z0-9_$]+)/);
|
|
532
|
+
const enumMatch = trimmed.match(/^(?:export\s+|const\s+|declare\s+)*enum\s+([A-Za-z0-9_$]+)/);
|
|
533
|
+
const fnMatch = trimmed.match(/^(?:export\s+|default\s+|async\s+|declare\s+)*function(?:\s*\*|\s+([A-Za-z0-9_$]+))/);
|
|
534
|
+
const constFnMatch = trimmed.match(/^(?:export\s+)?(?:const|let|var)\s+([A-Za-z0-9_$]+)\s*=\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z0-9_$]+)\s*=>/);
|
|
535
|
+
if (classMatch) {
|
|
536
|
+
const className = classMatch[1];
|
|
537
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
538
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
539
|
+
symbols.push({
|
|
540
|
+
symbol: className,
|
|
541
|
+
startLine,
|
|
542
|
+
endLine,
|
|
543
|
+
kind: 'class',
|
|
544
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
545
|
+
});
|
|
546
|
+
// Inspect inner members of class
|
|
547
|
+
for (let m = i + 1; m < endLine; m++) {
|
|
548
|
+
const mLine = lines[m].trim();
|
|
549
|
+
if (!mLine || isCommentOrEmpty(mLine, ext))
|
|
550
|
+
continue;
|
|
551
|
+
const methodMatch = mLine.match(/^(?:public\s+|private\s+|protected\s+|static\s+|async\s+|abstract\s+|override\s+|readonly\s+|get\s+|set\s+)*(constructor|[A-Za-z0-9_$]+)\s*(?:<[^>]+>)?\s*\(/);
|
|
552
|
+
if (methodMatch) {
|
|
553
|
+
const mName = methodMatch[1];
|
|
554
|
+
const mStart = findPrecedingContext(lines, m, ext);
|
|
555
|
+
const mEnd = findBlockEnd(lines, m, ext);
|
|
556
|
+
symbols.push({
|
|
557
|
+
symbol: `${className}.${mName}`,
|
|
558
|
+
parentSymbol: className,
|
|
559
|
+
startLine: mStart,
|
|
560
|
+
endLine: mEnd,
|
|
561
|
+
kind: mName === 'constructor' ? 'constructor' : 'method',
|
|
562
|
+
signature: extractCleanSignature(lines, m, mEnd),
|
|
563
|
+
});
|
|
564
|
+
m = mEnd;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
i = endLine + 1;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (ifaceMatch) {
|
|
571
|
+
const ifaceName = ifaceMatch[1];
|
|
572
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
573
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
574
|
+
symbols.push({
|
|
575
|
+
symbol: ifaceName,
|
|
576
|
+
startLine,
|
|
577
|
+
endLine,
|
|
578
|
+
kind: 'interface',
|
|
579
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
580
|
+
});
|
|
581
|
+
i = endLine + 1;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (typeMatch) {
|
|
585
|
+
const typeName = typeMatch[1];
|
|
586
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
587
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
588
|
+
symbols.push({
|
|
589
|
+
symbol: typeName,
|
|
590
|
+
startLine,
|
|
591
|
+
endLine,
|
|
592
|
+
kind: 'type',
|
|
593
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
594
|
+
});
|
|
595
|
+
i = endLine + 1;
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
if (enumMatch) {
|
|
599
|
+
const enumName = enumMatch[1];
|
|
600
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
601
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
602
|
+
symbols.push({
|
|
603
|
+
symbol: enumName,
|
|
604
|
+
startLine,
|
|
605
|
+
endLine,
|
|
606
|
+
kind: 'enum',
|
|
607
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
608
|
+
});
|
|
609
|
+
i = endLine + 1;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (fnMatch && fnMatch[1]) {
|
|
613
|
+
const fnName = fnMatch[1];
|
|
614
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
615
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
616
|
+
symbols.push({
|
|
617
|
+
symbol: fnName,
|
|
618
|
+
startLine,
|
|
619
|
+
endLine,
|
|
620
|
+
kind: 'function',
|
|
621
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
622
|
+
});
|
|
623
|
+
i = endLine + 1;
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
if (constFnMatch) {
|
|
627
|
+
const fnName = constFnMatch[1];
|
|
628
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
629
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
630
|
+
symbols.push({
|
|
631
|
+
symbol: fnName,
|
|
632
|
+
startLine,
|
|
633
|
+
endLine,
|
|
634
|
+
kind: 'function',
|
|
635
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
636
|
+
});
|
|
637
|
+
i = endLine + 1;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
else if (ext === '.py' || ext === '.pyi') {
|
|
642
|
+
const classMatch = trimmed.match(/^class\s+([A-Za-z0-9_$]+)/);
|
|
643
|
+
const fnMatch = trimmed.match(/^(?:async\s+)?def\s+([A-Za-z0-9_$]+)/);
|
|
644
|
+
if (classMatch) {
|
|
645
|
+
const className = classMatch[1];
|
|
646
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
647
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
648
|
+
symbols.push({
|
|
649
|
+
symbol: className,
|
|
650
|
+
startLine,
|
|
651
|
+
endLine,
|
|
652
|
+
kind: 'class',
|
|
653
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
654
|
+
});
|
|
655
|
+
// Inspect inner methods
|
|
656
|
+
for (let m = i + 1; m <= endLine; m++) {
|
|
657
|
+
const mLine = lines[m];
|
|
658
|
+
const mTrim = mLine.trim();
|
|
659
|
+
const mMatch = mTrim.match(/^(?:async\s+)?def\s+([A-Za-z0-9_$]+)/);
|
|
660
|
+
if (mMatch) {
|
|
661
|
+
const mName = mMatch[1];
|
|
662
|
+
const mStart = findPrecedingContext(lines, m, ext);
|
|
663
|
+
const mEnd = findBlockEnd(lines, m, ext);
|
|
664
|
+
symbols.push({
|
|
665
|
+
symbol: `${className}.${mName}`,
|
|
666
|
+
parentSymbol: className,
|
|
667
|
+
startLine: mStart,
|
|
668
|
+
endLine: mEnd,
|
|
669
|
+
kind: 'method',
|
|
670
|
+
signature: extractCleanSignature(lines, m, mEnd),
|
|
671
|
+
});
|
|
672
|
+
m = mEnd;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
i = endLine + 1;
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
if (fnMatch) {
|
|
679
|
+
const fnName = fnMatch[1];
|
|
680
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
681
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
682
|
+
symbols.push({
|
|
683
|
+
symbol: fnName,
|
|
684
|
+
startLine,
|
|
685
|
+
endLine,
|
|
686
|
+
kind: 'function',
|
|
687
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
688
|
+
});
|
|
689
|
+
i = endLine + 1;
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
else if (ext === '.go') {
|
|
694
|
+
const typeMatch = trimmed.match(/^type\s+([A-Za-z0-9_$]+)\s+(struct|interface|\w+)/);
|
|
695
|
+
const funcMatch = trimmed.match(/^func\s+(?:\((?:[A-Za-z0-9_$*]+\s+)?([A-Za-z0-9_$*]+)\)\s+)?([A-Za-z0-9_$]+)/);
|
|
696
|
+
if (typeMatch) {
|
|
697
|
+
const typeName = typeMatch[1];
|
|
698
|
+
const typeKind = typeMatch[2] === 'struct' ? 'struct' : typeMatch[2] === 'interface' ? 'interface' : 'type';
|
|
699
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
700
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
701
|
+
symbols.push({
|
|
702
|
+
symbol: typeName,
|
|
703
|
+
startLine,
|
|
704
|
+
endLine,
|
|
705
|
+
kind: typeKind,
|
|
706
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
707
|
+
});
|
|
708
|
+
i = endLine + 1;
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (funcMatch) {
|
|
712
|
+
const receiver = funcMatch[1]?.replace('*', '');
|
|
713
|
+
const fnName = funcMatch[2];
|
|
714
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
715
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
716
|
+
symbols.push({
|
|
717
|
+
symbol: receiver ? `${receiver}.${fnName}` : fnName,
|
|
718
|
+
parentSymbol: receiver,
|
|
719
|
+
startLine,
|
|
720
|
+
endLine,
|
|
721
|
+
kind: receiver ? 'method' : 'function',
|
|
722
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
723
|
+
});
|
|
724
|
+
i = endLine + 1;
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else if (ext === '.rs') {
|
|
729
|
+
const structMatch = trimmed.match(/^(?:pub(?:\([^)]+\))?\s+)?struct\s+([A-Za-z0-9_$]+)/);
|
|
730
|
+
const enumMatch = trimmed.match(/^(?:pub(?:\([^)]+\))?\s+)?enum\s+([A-Za-z0-9_$]+)/);
|
|
731
|
+
const traitMatch = trimmed.match(/^(?:pub(?:\([^)]+\))?\s+)?trait\s+([A-Za-z0-9_$]+)/);
|
|
732
|
+
const implMatch = trimmed.match(/^(?:pub(?:\([^)]+\))?\s+)?impl(?:\s+<[^>]+>)?\s+(?:([A-Za-z0-9_$]+)\s+for\s+)?([A-Za-z0-9_$]+)/);
|
|
733
|
+
const fnMatch = trimmed.match(/^(?:pub(?:\([^)]+\))?\s+)?(?:async\s+|const\s+|unsafe\s+|extern\s+)?fn\s+([A-Za-z0-9_$]+)/);
|
|
734
|
+
if (structMatch) {
|
|
735
|
+
const name = structMatch[1];
|
|
736
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
737
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
738
|
+
symbols.push({
|
|
739
|
+
symbol: name,
|
|
740
|
+
startLine,
|
|
741
|
+
endLine,
|
|
742
|
+
kind: 'struct',
|
|
743
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
744
|
+
});
|
|
745
|
+
i = endLine + 1;
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (enumMatch) {
|
|
749
|
+
const name = enumMatch[1];
|
|
750
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
751
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
752
|
+
symbols.push({
|
|
753
|
+
symbol: name,
|
|
754
|
+
startLine,
|
|
755
|
+
endLine,
|
|
756
|
+
kind: 'enum',
|
|
757
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
758
|
+
});
|
|
759
|
+
i = endLine + 1;
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
if (traitMatch) {
|
|
763
|
+
const name = traitMatch[1];
|
|
764
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
765
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
766
|
+
symbols.push({
|
|
767
|
+
symbol: name,
|
|
768
|
+
startLine,
|
|
769
|
+
endLine,
|
|
770
|
+
kind: 'trait',
|
|
771
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
772
|
+
});
|
|
773
|
+
i = endLine + 1;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (implMatch) {
|
|
777
|
+
const traitName = implMatch[1];
|
|
778
|
+
const targetType = implMatch[2];
|
|
779
|
+
const implName = traitName ? `${traitName} for ${targetType}` : targetType;
|
|
780
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
781
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
782
|
+
symbols.push({
|
|
783
|
+
symbol: implName,
|
|
784
|
+
parentSymbol: targetType,
|
|
785
|
+
startLine,
|
|
786
|
+
endLine,
|
|
787
|
+
kind: 'impl',
|
|
788
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
789
|
+
});
|
|
790
|
+
// Inspect inner methods in impl
|
|
791
|
+
for (let m = i + 1; m < endLine; m++) {
|
|
792
|
+
const mLine = lines[m].trim();
|
|
793
|
+
const mFnMatch = mLine.match(/^(?:pub(?:\([^)]+\))?\s+)?(?:async\s+|const\s+|unsafe\s+|extern\s+)?fn\s+([A-Za-z0-9_$]+)/);
|
|
794
|
+
if (mFnMatch) {
|
|
795
|
+
const fnName = mFnMatch[1];
|
|
796
|
+
const mStart = findPrecedingContext(lines, m, ext);
|
|
797
|
+
const mEnd = findBlockEnd(lines, m, ext);
|
|
798
|
+
symbols.push({
|
|
799
|
+
symbol: `${targetType}.${fnName}`,
|
|
800
|
+
parentSymbol: targetType,
|
|
801
|
+
startLine: mStart,
|
|
802
|
+
endLine: mEnd,
|
|
803
|
+
kind: 'method',
|
|
804
|
+
signature: extractCleanSignature(lines, m, mEnd),
|
|
805
|
+
});
|
|
806
|
+
m = mEnd;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
i = endLine + 1;
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (fnMatch) {
|
|
813
|
+
const fnName = fnMatch[1];
|
|
814
|
+
const startLine = findPrecedingContext(lines, i, ext);
|
|
815
|
+
const endLine = findBlockEnd(lines, i, ext);
|
|
816
|
+
symbols.push({
|
|
817
|
+
symbol: fnName,
|
|
818
|
+
startLine,
|
|
819
|
+
endLine,
|
|
820
|
+
kind: 'function',
|
|
821
|
+
signature: extractCleanSignature(lines, i, endLine),
|
|
822
|
+
});
|
|
823
|
+
i = endLine + 1;
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
i++;
|
|
310
828
|
}
|
|
311
829
|
return symbols;
|
|
312
830
|
}
|
|
313
831
|
/**
|
|
314
|
-
*
|
|
832
|
+
* Chunks a source code file into discrete, structured {@link DefinitionChunk} objects.
|
|
833
|
+
* Each chunk encapsulates a class, interface, function, method, struct, or type declaration
|
|
834
|
+
* along with its line range, signature, estimated token count, and content.
|
|
835
|
+
*
|
|
836
|
+
* @param content - Source file content
|
|
837
|
+
* @param filePath - File path used for identifier naming and language rules
|
|
838
|
+
* @param options - Chunking configuration options
|
|
839
|
+
* @returns Array of DefinitionChunk objects
|
|
840
|
+
*/
|
|
841
|
+
export function chunkDefinitions(content, filePath, options) {
|
|
842
|
+
if (!content || !content.trim())
|
|
843
|
+
return [];
|
|
844
|
+
const symbols = extractSymbolIndex(content, filePath);
|
|
845
|
+
const lines = content.split('\n');
|
|
846
|
+
const maxChunkLines = options?.maxChunkLines ?? 500;
|
|
847
|
+
const includeBodies = options?.includeBodies ?? true;
|
|
848
|
+
const filterKinds = options?.filterKinds ? new Set(options.filterKinds) : null;
|
|
849
|
+
const chunks = [];
|
|
850
|
+
for (const sym of symbols) {
|
|
851
|
+
if (filterKinds && sym.kind && !filterKinds.has(sym.kind))
|
|
852
|
+
continue;
|
|
853
|
+
let chunkLines = lines.slice(sym.startLine, sym.endLine + 1);
|
|
854
|
+
if (!includeBodies && (sym.kind === 'function' || sym.kind === 'method' || sym.kind === 'class')) {
|
|
855
|
+
const sig = sym.signature || extractCleanSignature(lines, sym.startLine, sym.endLine);
|
|
856
|
+
chunkLines = [sig + ' { ... }'];
|
|
857
|
+
}
|
|
858
|
+
else if (chunkLines.length > maxChunkLines) {
|
|
859
|
+
const truncated = chunkLines.slice(0, maxChunkLines);
|
|
860
|
+
truncated.push(`// ... [Truncated: ${chunkLines.length - maxChunkLines} additional lines omitted]`);
|
|
861
|
+
chunkLines = truncated;
|
|
862
|
+
}
|
|
863
|
+
const chunkContent = chunkLines.join('\n');
|
|
864
|
+
const chunkId = `${filePath}:${sym.symbol}`;
|
|
865
|
+
chunks.push({
|
|
866
|
+
id: chunkId,
|
|
867
|
+
symbol: sym.symbol,
|
|
868
|
+
parentSymbol: sym.parentSymbol,
|
|
869
|
+
kind: sym.kind || 'other',
|
|
870
|
+
startLine: sym.startLine,
|
|
871
|
+
endLine: sym.endLine,
|
|
872
|
+
signature: sym.signature || extractCleanSignature(lines, sym.startLine, sym.endLine),
|
|
873
|
+
doc: sym.doc,
|
|
874
|
+
content: chunkContent,
|
|
875
|
+
tokenEstimate: estimateTokens(chunkContent),
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
return chunks;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Extracts specified target symbols (functions, classes, methods, variables, interfaces, etc.) from source file content
|
|
315
882
|
* using multi-language regex declarations, doc/decorator context gathering, and balanced brace/indentation block parsing.
|
|
316
883
|
*
|
|
884
|
+
* Supports dotted / scoped member targeting (e.g. `['UserService.getUser']` or `['User#new']`).
|
|
885
|
+
*
|
|
317
886
|
* This function significantly reduces token usage when reading large files by returning only the requested symbols
|
|
318
887
|
* along with their context and omission separators.
|
|
319
888
|
*
|
|
320
889
|
* @param content - The full raw string content of the source file
|
|
321
890
|
* @param filePath - The file path (used to determine language syntax rules via file extension)
|
|
322
891
|
* @param targetElements - An array of symbol names to extract (e.g. `['extractSymbols', 'ExtractedSymbol']`)
|
|
892
|
+
* @param options - Formatting and chunking options
|
|
323
893
|
* @returns The filtered source string containing only the matched symbol blocks and omission markers
|
|
324
894
|
*/
|
|
325
|
-
export function extractSymbols(content, filePath, targetElements) {
|
|
895
|
+
export function extractSymbols(content, filePath, targetElements, options) {
|
|
326
896
|
if (!targetElements || targetElements.length === 0)
|
|
327
897
|
return content;
|
|
328
898
|
const ext = path.extname(filePath).toLowerCase();
|
|
329
899
|
const lines = content.split('\n');
|
|
330
900
|
const linesToKeep = new Set();
|
|
331
|
-
const
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
validLines[i] = false;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
const symbolRegexes = targetElements.map((symbol) => ({ symbol, regex: buildDeclarationRegex(symbol, ext) }));
|
|
341
|
-
let i = 0;
|
|
342
|
-
while (i < lines.length) {
|
|
343
|
-
if (!validLines[i]) {
|
|
344
|
-
i++;
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
const matched = findMatchedRegex(symbolRegexes, lines[i]);
|
|
348
|
-
if (matched) {
|
|
349
|
-
const startIndex = findPrecedingContext(lines, i, ext);
|
|
350
|
-
const endIndex = findBlockEnd(lines, i, ext);
|
|
351
|
-
addLinesToKeep(linesToKeep, startIndex, endIndex);
|
|
352
|
-
i = endIndex + 1;
|
|
353
|
-
}
|
|
354
|
-
else {
|
|
355
|
-
i++;
|
|
901
|
+
const metadata = extractSymbolMetadata(content, filePath, targetElements);
|
|
902
|
+
for (const meta of metadata) {
|
|
903
|
+
let start = meta.startLine;
|
|
904
|
+
let end = meta.endLine;
|
|
905
|
+
if (options?.maxLinesPerSymbol && end - start + 1 > options.maxLinesPerSymbol) {
|
|
906
|
+
end = start + options.maxLinesPerSymbol - 1;
|
|
356
907
|
}
|
|
908
|
+
addLinesToKeep(linesToKeep, start, end);
|
|
357
909
|
}
|
|
358
910
|
if (linesToKeep.size === 0) {
|
|
359
911
|
return `// No specific symbols matching ${JSON.stringify(targetElements)} were found in this file.\n// Try using normal read_file without targetElements, or check spelling.`;
|