@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.
@@ -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
+ }