@tarunpahade/asl-parser 1.0.1
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/LICENSE +201 -0
- package/README.md +226 -0
- package/bin/asl.js +127 -0
- package/lib/commands/rag-command.js +204 -0
- package/lib/parser.js +335 -0
- package/lib/promptAnalyzer.js +358 -0
- package/lib/rag/minimal-rag.js +170 -0
- package/package.json +42 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { analyzeFile, parseCodebase } = require('./parser');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract keywords from a prompt
|
|
7
|
+
*/
|
|
8
|
+
function extractKeywords(prompt) {
|
|
9
|
+
const lowerPrompt = prompt.toLowerCase();
|
|
10
|
+
|
|
11
|
+
// Common action words
|
|
12
|
+
const actionWords = ['add', 'create', 'update', 'modify', 'change', 'fix', 'remove', 'delete', 'implement', 'refactor'];
|
|
13
|
+
|
|
14
|
+
// Extract potential function/class names (words that start with capital or camelCase)
|
|
15
|
+
const nameMatches = prompt.match(/\b([A-Z][a-zA-Z0-9]+|[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*)\b/g) || [];
|
|
16
|
+
|
|
17
|
+
// Extract file extensions mentioned
|
|
18
|
+
const extMatches = prompt.match(/\.(js|ts|jsx|tsx|py|java|cpp|c|h|go|rs|rb|php|swift|kt|vue|svelte|html|css|scss|less|json|yaml|yml|toml|xml)\b/gi) || [];
|
|
19
|
+
|
|
20
|
+
// Extract quoted strings (likely file paths or names)
|
|
21
|
+
const quotedMatches = prompt.match(/["']([^"']+)["']/g) || [];
|
|
22
|
+
|
|
23
|
+
// Extract file paths (containing / or \)
|
|
24
|
+
const pathMatches = prompt.match(/[\/\\][\w\-\.\/\\]+/g) || [];
|
|
25
|
+
|
|
26
|
+
// Extract common technical terms
|
|
27
|
+
const techTerms = [];
|
|
28
|
+
const techKeywords = ['api', 'endpoint', 'route', 'component', 'function', 'class', 'method', 'variable', 'constant', 'config', 'database', 'db', 'model', 'view', 'controller', 'service', 'util', 'helper', 'handler', 'middleware'];
|
|
29
|
+
techKeywords.forEach(term => {
|
|
30
|
+
if (lowerPrompt.includes(term)) techTerms.push(term);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
actionWords: actionWords.filter(word => lowerPrompt.includes(word)),
|
|
35
|
+
names: nameMatches,
|
|
36
|
+
extensions: extMatches.map(ext => ext.toLowerCase()),
|
|
37
|
+
quoted: quotedMatches.map(q => q.replace(/["']/g, '')),
|
|
38
|
+
paths: pathMatches,
|
|
39
|
+
techTerms: techTerms,
|
|
40
|
+
allKeywords: lowerPrompt.split(/\s+/).filter(word => word.length > 2)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Score how relevant a file is to the prompt
|
|
46
|
+
*/
|
|
47
|
+
function scoreFileRelevance(fileAnalysis, keywords, prompt) {
|
|
48
|
+
let score = 0;
|
|
49
|
+
const lowerPrompt = prompt.toLowerCase();
|
|
50
|
+
const fileName = fileAnalysis.fileName.toLowerCase();
|
|
51
|
+
const filePath = fileAnalysis.path.toLowerCase();
|
|
52
|
+
|
|
53
|
+
// Check file name matches
|
|
54
|
+
keywords.names.forEach(name => {
|
|
55
|
+
if (fileName.includes(name.toLowerCase()) || filePath.includes(name.toLowerCase())) {
|
|
56
|
+
score += 10;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Check file path matches
|
|
61
|
+
keywords.paths.forEach(p => {
|
|
62
|
+
if (filePath.includes(p.toLowerCase())) {
|
|
63
|
+
score += 15;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Check quoted strings (likely file names)
|
|
68
|
+
keywords.quoted.forEach(q => {
|
|
69
|
+
if (fileName.includes(q.toLowerCase()) || filePath.includes(q.toLowerCase())) {
|
|
70
|
+
score += 20;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Check extension matches
|
|
75
|
+
if (keywords.extensions.length > 0 && keywords.extensions.includes(fileAnalysis.extension)) {
|
|
76
|
+
score += 5;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check function/class name matches
|
|
80
|
+
const allNames = [...fileAnalysis.functions, ...fileAnalysis.classes];
|
|
81
|
+
keywords.names.forEach(keyword => {
|
|
82
|
+
allNames.forEach(name => {
|
|
83
|
+
if (name.toLowerCase().includes(keyword.toLowerCase()) || keyword.toLowerCase().includes(name.toLowerCase())) {
|
|
84
|
+
score += 8;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Check tech terms in file path
|
|
90
|
+
keywords.techTerms.forEach(term => {
|
|
91
|
+
if (filePath.includes(term) || fileName.includes(term)) {
|
|
92
|
+
score += 5;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Check if any keyword appears in the file path
|
|
97
|
+
keywords.allKeywords.forEach(keyword => {
|
|
98
|
+
if (filePath.includes(keyword) || fileName.includes(keyword)) {
|
|
99
|
+
score += 2;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return score;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Score how relevant a function/class is to the prompt
|
|
108
|
+
*/
|
|
109
|
+
function scoreCodeRelevance(codeName, keywords, prompt) {
|
|
110
|
+
let score = 0;
|
|
111
|
+
const lowerName = codeName.toLowerCase();
|
|
112
|
+
const lowerPrompt = prompt.toLowerCase();
|
|
113
|
+
|
|
114
|
+
// Exact or partial name match
|
|
115
|
+
keywords.names.forEach(name => {
|
|
116
|
+
if (lowerName.includes(name.toLowerCase()) || name.toLowerCase().includes(lowerName)) {
|
|
117
|
+
score += 15;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Check if function name contains action words
|
|
122
|
+
keywords.actionWords.forEach(action => {
|
|
123
|
+
if (lowerName.includes(action)) {
|
|
124
|
+
score += 5;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Check if prompt mentions the function/class name
|
|
129
|
+
if (lowerPrompt.includes(lowerName) || lowerName.includes(lowerPrompt.split(/\s+/)[0])) {
|
|
130
|
+
score += 20;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return score;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Find relevant lines in a file based on keywords
|
|
138
|
+
*/
|
|
139
|
+
function findRelevantLines(filePath, keywords, prompt) {
|
|
140
|
+
try {
|
|
141
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
142
|
+
const lines = content.split('\n');
|
|
143
|
+
const relevantLines = [];
|
|
144
|
+
|
|
145
|
+
lines.forEach((line, index) => {
|
|
146
|
+
const lowerLine = line.toLowerCase();
|
|
147
|
+
let lineScore = 0;
|
|
148
|
+
|
|
149
|
+
// Check for keyword matches
|
|
150
|
+
keywords.names.forEach(name => {
|
|
151
|
+
if (lowerLine.includes(name.toLowerCase())) {
|
|
152
|
+
lineScore += 5;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
keywords.actionWords.forEach(action => {
|
|
157
|
+
if (lowerLine.includes(action)) {
|
|
158
|
+
lineScore += 3;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
keywords.techTerms.forEach(term => {
|
|
163
|
+
if (lowerLine.includes(term)) {
|
|
164
|
+
lineScore += 2;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Check for function/class definitions
|
|
169
|
+
if (line.match(/(function|class|const|let|var)\s+\w+/i)) {
|
|
170
|
+
lineScore += 10;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (lineScore > 0) {
|
|
174
|
+
relevantLines.push({
|
|
175
|
+
lineNumber: index + 1,
|
|
176
|
+
content: line.trim(),
|
|
177
|
+
score: lineScore
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Sort by score and return top matches
|
|
183
|
+
return relevantLines
|
|
184
|
+
.sort((a, b) => b.score - a.score)
|
|
185
|
+
.slice(0, 20); // Top 20 most relevant lines
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Analyze prompt and find impacted code
|
|
193
|
+
*/
|
|
194
|
+
async function analyzePrompt(basePath, userPrompt) {
|
|
195
|
+
const keywords = extractKeywords(userPrompt);
|
|
196
|
+
|
|
197
|
+
// First, get codebase structure
|
|
198
|
+
const files = [];
|
|
199
|
+
const { glob } = require('glob');
|
|
200
|
+
const CODE_EXTENSIONS = [
|
|
201
|
+
'.js', '.jsx', '.ts', '.tsx', '.py', '.java', '.cpp', '.c', '.h',
|
|
202
|
+
'.cs', '.go', '.rs', '.rb', '.php', '.swift', '.kt', '.scala',
|
|
203
|
+
'.vue', '.svelte', '.html', '.css', '.scss', '.less',
|
|
204
|
+
'.json', '.yaml', '.yml', '.toml', '.xml'
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const IGNORE_DIRS = [
|
|
208
|
+
'node_modules', '.git', '.next', '.nuxt', 'dist', 'build',
|
|
209
|
+
'.cache', 'coverage', '.vscode', '.idea', 'vendor', '__pycache__',
|
|
210
|
+
'.pytest_cache', 'target', 'bin', 'obj'
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
// Find all code files
|
|
214
|
+
const patterns = CODE_EXTENSIONS.map(ext => `**/*${ext}`);
|
|
215
|
+
|
|
216
|
+
for (const pattern of patterns) {
|
|
217
|
+
try {
|
|
218
|
+
const foundFiles = await glob(pattern, {
|
|
219
|
+
cwd: basePath,
|
|
220
|
+
absolute: true,
|
|
221
|
+
ignore: IGNORE_DIRS.map(dir => `**/${dir}/**`)
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
for (const filePath of foundFiles) {
|
|
225
|
+
const relativePath = path.relative(basePath, filePath);
|
|
226
|
+
const parts = relativePath.split(path.sep);
|
|
227
|
+
|
|
228
|
+
// Skip ignored directories
|
|
229
|
+
if (parts.some(part => IGNORE_DIRS.includes(part) || part.startsWith('.'))) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
files.push(filePath);
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
// Continue on error
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Analyze each file
|
|
241
|
+
const fileScores = [];
|
|
242
|
+
for (const filePath of files) {
|
|
243
|
+
const analysis = analyzeFile(filePath);
|
|
244
|
+
if (analysis.error) continue;
|
|
245
|
+
|
|
246
|
+
const fileScore = scoreFileRelevance(analysis, keywords, userPrompt);
|
|
247
|
+
|
|
248
|
+
if (fileScore > 0) {
|
|
249
|
+
// Score functions and classes
|
|
250
|
+
const relevantFunctions = analysis.functions
|
|
251
|
+
.map(func => ({
|
|
252
|
+
name: func,
|
|
253
|
+
score: scoreCodeRelevance(func, keywords, userPrompt),
|
|
254
|
+
type: 'function'
|
|
255
|
+
}))
|
|
256
|
+
.filter(item => item.score > 0)
|
|
257
|
+
.sort((a, b) => b.score - a.score);
|
|
258
|
+
|
|
259
|
+
const relevantClasses = analysis.classes
|
|
260
|
+
.map(cls => ({
|
|
261
|
+
name: cls,
|
|
262
|
+
score: scoreCodeRelevance(cls, keywords, userPrompt),
|
|
263
|
+
type: 'class'
|
|
264
|
+
}))
|
|
265
|
+
.filter(item => item.score > 0)
|
|
266
|
+
.sort((a, b) => b.score - a.score);
|
|
267
|
+
|
|
268
|
+
// Find relevant lines
|
|
269
|
+
const relevantLines = findRelevantLines(filePath, keywords, userPrompt);
|
|
270
|
+
|
|
271
|
+
fileScores.push({
|
|
272
|
+
file: analysis,
|
|
273
|
+
score: fileScore,
|
|
274
|
+
relevantFunctions,
|
|
275
|
+
relevantClasses,
|
|
276
|
+
relevantLines: relevantLines.slice(0, 10) // Top 10 lines
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Sort by score and return top matches
|
|
282
|
+
return fileScores
|
|
283
|
+
.sort((a, b) => b.score - a.score)
|
|
284
|
+
.slice(0, 20); // Top 20 most relevant files
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Generate impact report
|
|
289
|
+
*/
|
|
290
|
+
function generateImpactReport(results, prompt) {
|
|
291
|
+
let report = `# Impact Analysis Report\n\n`;
|
|
292
|
+
report += `**Prompt:** "${prompt}"\n\n`;
|
|
293
|
+
report += `**Generated:** ${new Date().toISOString()}\n\n`;
|
|
294
|
+
report += `---\n\n`;
|
|
295
|
+
|
|
296
|
+
if (results.length === 0) {
|
|
297
|
+
report += `## No Matches Found\n\n`;
|
|
298
|
+
report += `No files, functions, or code sections were found matching your prompt.\n`;
|
|
299
|
+
report += `Try using more specific keywords like function names, file paths, or technical terms.\n\n`;
|
|
300
|
+
return report;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
report += `## Summary\n\n`;
|
|
304
|
+
report += `- **Files Affected:** ${results.length}\n`;
|
|
305
|
+
const totalFunctions = results.reduce((sum, r) => sum + r.relevantFunctions.length, 0);
|
|
306
|
+
const totalClasses = results.reduce((sum, r) => sum + r.relevantClasses.length, 0);
|
|
307
|
+
report += `- **Functions Impacted:** ${totalFunctions}\n`;
|
|
308
|
+
report += `- **Classes Impacted:** ${totalClasses}\n\n`;
|
|
309
|
+
|
|
310
|
+
report += `---\n\n`;
|
|
311
|
+
report += `## Detailed Impact Analysis\n\n`;
|
|
312
|
+
|
|
313
|
+
results.forEach((result, index) => {
|
|
314
|
+
const file = result.file;
|
|
315
|
+
report += `### ${index + 1}. \`${file.path}\`\n\n`;
|
|
316
|
+
report += `**Relevance Score:** ${result.score}\n\n`;
|
|
317
|
+
report += `- **Extension:** ${file.extension || '(none)'}\n`;
|
|
318
|
+
report += `- **Total Lines:** ${file.totalLines}\n`;
|
|
319
|
+
report += `- **Size:** ${(file.size / 1024).toFixed(2)} KB\n\n`;
|
|
320
|
+
|
|
321
|
+
if (result.relevantFunctions.length > 0) {
|
|
322
|
+
report += `#### Functions Impacted:\n\n`;
|
|
323
|
+
result.relevantFunctions.forEach(func => {
|
|
324
|
+
report += `- **${func.name}** (score: ${func.score})\n`;
|
|
325
|
+
});
|
|
326
|
+
report += `\n`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (result.relevantClasses.length > 0) {
|
|
330
|
+
report += `#### Classes Impacted:\n\n`;
|
|
331
|
+
result.relevantClasses.forEach(cls => {
|
|
332
|
+
report += `- **${cls.name}** (score: ${cls.score})\n`;
|
|
333
|
+
});
|
|
334
|
+
report += `\n`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (result.relevantLines.length > 0) {
|
|
338
|
+
report += `#### Relevant Lines:\n\n`;
|
|
339
|
+
report += `\`\`\`\n`;
|
|
340
|
+
result.relevantLines.forEach(line => {
|
|
341
|
+
report += `${line.lineNumber}: ${line.content}\n`;
|
|
342
|
+
});
|
|
343
|
+
report += `\`\`\`\n\n`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
report += `---\n\n`;
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
report += `## Recommendations\n\n`;
|
|
350
|
+
report += `1. **Review the top-scoring files first** - They are most likely to be affected by your changes\n`;
|
|
351
|
+
report += `2. **Check function/class dependencies** - Functions and classes listed may need updates\n`;
|
|
352
|
+
report += `3. **Examine relevant lines** - The specific lines shown may require modifications\n`;
|
|
353
|
+
report += `4. **Consider related files** - Files in the same directory or with similar names may also be impacted\n\n`;
|
|
354
|
+
|
|
355
|
+
return report;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
module.exports = { analyzePrompt, generateImpactReport, extractKeywords };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// src/rag/minimal-rag.js
|
|
2
|
+
// Drop-in RAG module for asl-parser
|
|
3
|
+
// Usage: asl rag "your query"
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
class MinimalRAG {
|
|
9
|
+
constructor(codebasePath = './') {
|
|
10
|
+
this.codebasePath = codebasePath;
|
|
11
|
+
this.chunks = [];
|
|
12
|
+
this.initialized = false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Index all code files in the codebase
|
|
17
|
+
*/
|
|
18
|
+
async initialize(files) {
|
|
19
|
+
console.log('๐ Indexing codebase...');
|
|
20
|
+
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
try {
|
|
23
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
24
|
+
this.indexFile(file, content);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
// Skip unreadable files
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.initialized = true;
|
|
31
|
+
console.log(`โ
Indexed ${this.chunks.length} code sections\n`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Break a file into searchable chunks
|
|
36
|
+
*/
|
|
37
|
+
indexFile(filepath, content) {
|
|
38
|
+
const lines = content.split('\n');
|
|
39
|
+
const chunkSize = 20; // Lines per chunk
|
|
40
|
+
const overlap = 5;
|
|
41
|
+
|
|
42
|
+
// Create function/class aware chunks
|
|
43
|
+
for (let i = 0; i < lines.length; i += chunkSize - overlap) {
|
|
44
|
+
const chunk = lines.slice(i, i + chunkSize).join('\n');
|
|
45
|
+
|
|
46
|
+
if (chunk.trim().length > 0) {
|
|
47
|
+
this.chunks.push({
|
|
48
|
+
file: filepath,
|
|
49
|
+
lineStart: i + 1,
|
|
50
|
+
lineEnd: Math.min(i + chunkSize, lines.length),
|
|
51
|
+
content: chunk,
|
|
52
|
+
context: this.extractContext(chunk, filepath)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extract context (functions, classes, comments)
|
|
60
|
+
*/
|
|
61
|
+
extractContext(chunk, filepath) {
|
|
62
|
+
const contexts = [];
|
|
63
|
+
|
|
64
|
+
// Find function names
|
|
65
|
+
const funcMatch = chunk.match(/(?:function|const|async)\s+(\w+)\s*(?:=|:|\()/g);
|
|
66
|
+
if (funcMatch) contexts.push(...funcMatch.map(f => f.split(/\s+/)[1]));
|
|
67
|
+
|
|
68
|
+
// Find class names
|
|
69
|
+
const classMatch = chunk.match(/class\s+(\w+)/g);
|
|
70
|
+
if (classMatch) contexts.push(...classMatch.map(c => c.split(/\s+/)[1]));
|
|
71
|
+
|
|
72
|
+
// Get file path parts for context
|
|
73
|
+
contexts.push(...filepath.split('/').filter(p => p.length > 2));
|
|
74
|
+
|
|
75
|
+
return contexts.filter(Boolean);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Search for relevant code sections
|
|
80
|
+
*/
|
|
81
|
+
search(query, topK = 8) {
|
|
82
|
+
if (!this.initialized) {
|
|
83
|
+
throw new Error('RAG not initialized. Call initialize() first.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const queryTokens = query.toLowerCase().split(/\s+/);
|
|
87
|
+
const results = [];
|
|
88
|
+
|
|
89
|
+
for (const chunk of this.chunks) {
|
|
90
|
+
let score = 0;
|
|
91
|
+
const chunkText = chunk.content.toLowerCase();
|
|
92
|
+
const chunkContext = chunk.context.map(c => c.toLowerCase());
|
|
93
|
+
|
|
94
|
+
// Token matching in content (higher weight for exact matches)
|
|
95
|
+
for (const token of queryTokens) {
|
|
96
|
+
const contentMatches = (chunkText.match(new RegExp(`\\b${token}\\w*`, 'g')) || []).length;
|
|
97
|
+
score += contentMatches * 2;
|
|
98
|
+
|
|
99
|
+
// Context matching (lower weight)
|
|
100
|
+
const contextMatches = chunkContext.filter(c => c.includes(token)).length;
|
|
101
|
+
score += contextMatches;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Relevance boost for file path
|
|
105
|
+
if (queryTokens.some(t => chunk.file.toLowerCase().includes(t))) {
|
|
106
|
+
score += 3;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (score > 0) {
|
|
110
|
+
results.push({ ...chunk, score });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return results
|
|
115
|
+
.sort((a, b) => b.score - a.score)
|
|
116
|
+
.slice(0, topK);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Format results for display
|
|
121
|
+
*/
|
|
122
|
+
formatResults(results, query) {
|
|
123
|
+
if (results.length === 0) {
|
|
124
|
+
return `โ No relevant code found for: "${query}"`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let output = `โ
Found ${results.length} relevant sections:\n`;
|
|
128
|
+
output += `๐ Query: "${query}"\n`;
|
|
129
|
+
output += 'โ'.repeat(70) + '\n\n';
|
|
130
|
+
|
|
131
|
+
results.forEach((result, idx) => {
|
|
132
|
+
const relevance = Math.min(100, Math.round((result.score / 10) * 10));
|
|
133
|
+
const fileIcon = this.getFileIcon(result.file);
|
|
134
|
+
|
|
135
|
+
output += `${idx + 1}. ${fileIcon} ${result.file}\n`;
|
|
136
|
+
output += ` ๐ Lines ${result.lineStart}-${result.lineEnd} | `;
|
|
137
|
+
output += `๐ Relevance: ${relevance}%\n`;
|
|
138
|
+
|
|
139
|
+
if (result.context.length > 0) {
|
|
140
|
+
output += ` ๐ฏ Context: ${result.context.slice(0, 3).join(', ')}\n`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Show code snippet
|
|
144
|
+
const snippet = result.content
|
|
145
|
+
.split('\n')
|
|
146
|
+
.slice(0, 3)
|
|
147
|
+
.join('\n')
|
|
148
|
+
.substring(0, 80);
|
|
149
|
+
|
|
150
|
+
output += ` > ${snippet}...\n`;
|
|
151
|
+
output += '\n';
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return output;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getFileIcon(filepath) {
|
|
158
|
+
if (filepath.includes('.js')) return '๐';
|
|
159
|
+
if (filepath.includes('.ts')) return '๐';
|
|
160
|
+
if (filepath.includes('.py')) return '๐';
|
|
161
|
+
if (filepath.includes('.json')) return '๐';
|
|
162
|
+
if (filepath.includes('test')) return '๐งช';
|
|
163
|
+
if (filepath.includes('auth')) return '๐';
|
|
164
|
+
if (filepath.includes('api')) return '๐';
|
|
165
|
+
if (filepath.includes('db')) return '๐๏ธ';
|
|
166
|
+
return '๐';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = MinimalRAG;
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tarunpahade/asl-parser",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A CLI tool to parse and analyze codebases, generating agent specifications in agents.md",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"asl": "./bin/asl.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"asl",
|
|
14
|
+
"parser",
|
|
15
|
+
"codebase",
|
|
16
|
+
"analysis",
|
|
17
|
+
"agents",
|
|
18
|
+
"cli",
|
|
19
|
+
"code-analysis",
|
|
20
|
+
"documentation",
|
|
21
|
+
"markdown"
|
|
22
|
+
],
|
|
23
|
+
"author": "tpahade10",
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/tpahade10/asl-parser.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/tpahade10/asl-parser/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/tpahade10/asl-parser#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@tarunpahade/asl-parser": "^1.0.1",
|
|
35
|
+
"chalk": "^4.1.2",
|
|
36
|
+
"commander": "^11.1.0",
|
|
37
|
+
"glob": "^10.3.10"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=14.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|