@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,204 @@
1
+ // lib/rag-command.js
2
+ // Simple RAG search command for asl-parser
3
+ // Usage: asl rag "your search query"
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { glob } = require('glob');
8
+ const chalk = require('chalk');
9
+
10
+ // Same ignore patterns as parser.js
11
+ const IGNORE_DIRS = [
12
+ 'node_modules', '.git', '.next', '.nuxt', 'dist', 'build',
13
+ '.cache', 'coverage', '.vscode', '.idea', 'vendor',
14
+ '__pycache__', '.pytest_cache', 'target', 'bin', 'obj'
15
+ ];
16
+
17
+ const CODE_EXTENSIONS = [
18
+ '.js', '.jsx', '.ts', '.tsx', '.py', '.java', '.cpp', '.c', '.h',
19
+ '.cs', '.go', '.rs', '.rb', '.php', '.swift', '.kt', '.scala',
20
+ '.vue', '.svelte', '.html', '.css', '.scss', '.less',
21
+ '.json', '.yaml', '.yml', '.toml', '.xml'
22
+ ];
23
+
24
+ /**
25
+ * Simple RAG Search - finds relevant code sections
26
+ */
27
+ class RagSearch {
28
+ constructor(basePath) {
29
+ this.basePath = basePath;
30
+ this.chunks = [];
31
+ }
32
+
33
+ /**
34
+ * Index all code files by creating searchable chunks
35
+ */
36
+ async index() {
37
+ console.log(chalk.blue('📚 Indexing codebase...'));
38
+
39
+ const patterns = CODE_EXTENSIONS.map(ext => `**/*${ext}`);
40
+
41
+ for (const pattern of patterns) {
42
+ try {
43
+ const files = await glob(pattern, {
44
+ cwd: this.basePath,
45
+ absolute: true,
46
+ ignore: IGNORE_DIRS.map(dir => `**/${dir}/**`)
47
+ });
48
+
49
+ for (const filePath of files) {
50
+ this.indexFile(filePath);
51
+ }
52
+ } catch (error) {
53
+ // Silently skip glob errors
54
+ }
55
+ }
56
+
57
+ console.log(chalk.green(`✅ Indexed ${this.chunks.length} code sections\n`));
58
+ }
59
+
60
+ /**
61
+ * Break a file into searchable chunks
62
+ */
63
+ indexFile(filePath) {
64
+ try {
65
+ const content = fs.readFileSync(filePath, 'utf-8');
66
+ const relativePath = path.relative(this.basePath, filePath);
67
+ const lines = content.split('\n');
68
+ const chunkSize = 20;
69
+
70
+ // Create overlapping chunks
71
+ for (let i = 0; i < lines.length; i += chunkSize - 5) {
72
+ const chunk = lines.slice(i, i + chunkSize).join('\n');
73
+
74
+ if (chunk.trim().length > 0) {
75
+ this.chunks.push({
76
+ file: relativePath,
77
+ lineStart: i + 1,
78
+ lineEnd: Math.min(i + chunkSize, lines.length),
79
+ content: chunk,
80
+ keywords: this.extractKeywords(chunk, relativePath)
81
+ });
82
+ }
83
+ }
84
+ } catch (error) {
85
+ // Skip unreadable files
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Extract keywords from code chunk
91
+ */
92
+ extractKeywords(content, filePath) {
93
+ const keywords = [];
94
+
95
+ // Extract function/class names
96
+ const funcMatches = content.match(/(?:function|const|async|def|class)\s+(\w+)/g);
97
+ if (funcMatches) {
98
+ keywords.push(...funcMatches.map(f => f.split(/\s+/)[1]));
99
+ }
100
+
101
+ // Extract from file path
102
+ const pathParts = filePath.split('/').filter(p => p.length > 2);
103
+ keywords.push(...pathParts);
104
+
105
+ return keywords.map(k => k.toLowerCase()).filter(Boolean);
106
+ }
107
+
108
+ /**
109
+ * Search for relevant chunks
110
+ */
111
+ search(query, topK = 10) {
112
+ const queryTokens = query.toLowerCase().split(/\s+/);
113
+ const results = [];
114
+
115
+ for (const chunk of this.chunks) {
116
+ let score = 0;
117
+ const contentLower = chunk.content.toLowerCase();
118
+
119
+ // Score based on token matches
120
+ for (const token of queryTokens) {
121
+ // Content matches (high weight)
122
+ const contentMatches = (contentLower.match(new RegExp(`\\b${token}\\w*`, 'g')) || []).length;
123
+ score += contentMatches * 2;
124
+
125
+ // Keyword matches (medium weight)
126
+ const keywordMatches = chunk.keywords.filter(k => k.includes(token)).length;
127
+ score += keywordMatches * 1.5;
128
+ }
129
+
130
+ if (score > 0) {
131
+ results.push({ ...chunk, score });
132
+ }
133
+ }
134
+
135
+ return results
136
+ .sort((a, b) => b.score - a.score)
137
+ .slice(0, topK);
138
+ }
139
+
140
+ /**
141
+ * Format results for display
142
+ */
143
+ displayResults(results, query) {
144
+ if (results.length === 0) {
145
+ console.log(chalk.yellow(`❌ No relevant code found for: "${query}"\n`));
146
+ return;
147
+ }
148
+
149
+ console.log(chalk.green(`✅ Found ${results.length} relevant sections:\n`));
150
+ console.log(chalk.cyan(`📍 Query: "${query}"\n`));
151
+
152
+ results.forEach((result, idx) => {
153
+ const relevance = Math.min(100, Math.round((result.score / 5) * 10));
154
+ const fileIcon = result.file.includes('test') ? '🧪' : '📄';
155
+
156
+ console.log(chalk.yellow(`${idx + 1}. ${fileIcon} ${result.file}`));
157
+ console.log(chalk.gray(` Lines ${result.lineStart}-${result.lineEnd} | Relevance: ${relevance}%`));
158
+
159
+ // Show code snippet
160
+ const snippet = result.content
161
+ .split('\n')
162
+ .slice(0, 2)
163
+ .join('\n')
164
+ .substring(0, 70);
165
+
166
+ console.log(chalk.white(` ${snippet}...`));
167
+ console.log('');
168
+ });
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Main RAG command function
174
+ */
175
+ async function ragCommand(query, targetPath = './') {
176
+ try {
177
+ const absolutePath = path.resolve(targetPath);
178
+
179
+ if (!fs.existsSync(absolutePath)) {
180
+ console.error(chalk.red(`Error: Path "${targetPath}" does not exist`));
181
+ process.exit(1);
182
+ }
183
+
184
+ if (!fs.statSync(absolutePath).isDirectory()) {
185
+ console.error(chalk.red(`Error: "${targetPath}" is not a directory`));
186
+ process.exit(1);
187
+ }
188
+
189
+ console.log(chalk.blue(`\n🔍 RAG Search: "${query}"\n`));
190
+
191
+ // Initialize and search
192
+ const rag = new RagSearch(absolutePath);
193
+ await rag.index();
194
+
195
+ const results = rag.search(query, 10);
196
+ rag.displayResults(results, query);
197
+
198
+ } catch (error) {
199
+ console.error(chalk.red(`\nError: ${error.message}\n`));
200
+ process.exit(1);
201
+ }
202
+ }
203
+
204
+ module.exports = ragCommand;
package/lib/parser.js ADDED
@@ -0,0 +1,335 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { glob } = require('glob');
4
+
5
+ // File extensions to analyze
6
+ const CODE_EXTENSIONS = [
7
+ '.js', '.jsx', '.ts', '.tsx', '.py', '.java', '.cpp', '.c', '.h',
8
+ '.cs', '.go', '.rs', '.rb', '.php', '.swift', '.kt', '.scala',
9
+ '.vue', '.svelte', '.html', '.css', '.scss', '.less',
10
+ '.json', '.yaml', '.yml', '.toml', '.xml'
11
+ ];
12
+
13
+ // Directories to ignore
14
+ const IGNORE_DIRS = [
15
+ 'node_modules',
16
+ '.git',
17
+ '.next',
18
+ '.nuxt',
19
+ 'dist',
20
+ 'build',
21
+ '.cache',
22
+ 'coverage',
23
+ '.vscode',
24
+ '.idea',
25
+ 'vendor',
26
+ '__pycache__',
27
+ '.pytest_cache',
28
+ 'target',
29
+ 'bin',
30
+ 'obj'
31
+ ];
32
+
33
+ // Files to ignore
34
+ const IGNORE_FILES = [
35
+ 'package-lock.json',
36
+ 'yarn.lock',
37
+ 'pnpm-lock.yaml',
38
+ '.DS_Store',
39
+ 'Thumbs.db'
40
+ ];
41
+
42
+ /**
43
+ * Check if a file should be ignored
44
+ */
45
+ function shouldIgnore(filePath, basePath) {
46
+ const relativePath = path.relative(basePath, filePath);
47
+ const parts = relativePath.split(path.sep);
48
+
49
+ // Check if any part matches ignore directories
50
+ for (const part of parts) {
51
+ if (IGNORE_DIRS.includes(part) || part.startsWith('.')) {
52
+ return true;
53
+ }
54
+ }
55
+
56
+ // Check if file matches ignore patterns
57
+ const fileName = path.basename(filePath);
58
+ if (IGNORE_FILES.includes(fileName)) {
59
+ return true;
60
+ }
61
+
62
+ return false;
63
+ }
64
+
65
+ /**
66
+ * Get file extension
67
+ */
68
+ function getFileExtension(filePath) {
69
+ return path.extname(filePath).toLowerCase();
70
+ }
71
+
72
+ /**
73
+ * Analyze a single file
74
+ */
75
+ function analyzeFile(filePath) {
76
+ try {
77
+ const content = fs.readFileSync(filePath, 'utf-8');
78
+ const ext = getFileExtension(filePath);
79
+ const fileName = path.basename(filePath);
80
+ const relativePath = path.relative(process.cwd(), filePath);
81
+
82
+ // Basic analysis
83
+ const lines = content.split('\n');
84
+ const totalLines = lines.length;
85
+ const codeLines = lines.filter(line => line.trim() && !line.trim().startsWith('//') && !line.trim().startsWith('/*') && !line.trim().startsWith('*')).length;
86
+ const commentLines = totalLines - codeLines;
87
+
88
+ // Detect functions/classes (basic heuristics)
89
+ const functions = [];
90
+ const classes = [];
91
+
92
+ // JavaScript/TypeScript patterns
93
+ if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
94
+ // Match: function name(
95
+ const funcDeclMatches = content.matchAll(/function\s+(\w+)\s*\(/g);
96
+ for (const match of funcDeclMatches) {
97
+ functions.push(match[1]);
98
+ }
99
+
100
+ // Match: const name = (async)? (
101
+ const arrowFuncMatches = content.matchAll(/const\s+(\w+)\s*=\s*(?:async\s+)?\(/g);
102
+ for (const match of arrowFuncMatches) {
103
+ functions.push(match[1]);
104
+ }
105
+
106
+ // Match: name: (async)? (
107
+ const methodMatches = content.matchAll(/(\w+)\s*:\s*(?:async\s+)?\(/g);
108
+ for (const match of methodMatches) {
109
+ functions.push(match[1]);
110
+ }
111
+
112
+ const classMatches = content.matchAll(/class\s+(\w+)/g);
113
+ for (const match of classMatches) {
114
+ classes.push(match[1]);
115
+ }
116
+ }
117
+
118
+ // Python patterns
119
+ if (ext === '.py') {
120
+ const functionMatches = content.matchAll(/def\s+(\w+)/g);
121
+ for (const match of functionMatches) {
122
+ functions.push(match[1]);
123
+ }
124
+
125
+ const classMatches = content.matchAll(/class\s+(\w+)/g);
126
+ for (const match of classMatches) {
127
+ classes.push(match[1]);
128
+ }
129
+ }
130
+
131
+ // Java patterns
132
+ if (ext === '.java') {
133
+ const methodMatches = content.matchAll(/(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(/g);
134
+ for (const match of methodMatches) {
135
+ functions.push(match[1]);
136
+ }
137
+
138
+ const classMatches = content.matchAll(/class\s+(\w+)/g);
139
+ for (const match of classMatches) {
140
+ classes.push(match[1]);
141
+ }
142
+ }
143
+
144
+ return {
145
+ path: relativePath,
146
+ fileName,
147
+ extension: ext,
148
+ totalLines,
149
+ codeLines,
150
+ commentLines,
151
+ functions: [...new Set(functions)],
152
+ classes: [...new Set(classes)],
153
+ size: content.length
154
+ };
155
+ } catch (error) {
156
+ return {
157
+ path: path.relative(process.cwd(), filePath),
158
+ fileName: path.basename(filePath),
159
+ error: error.message
160
+ };
161
+ }
162
+ }
163
+
164
+
165
+ /**
166
+ * Parse entire codebase
167
+ */
168
+ async function parseCodebase(basePath, outputPath) {
169
+ const files = [];
170
+ const stats = {
171
+ totalFiles: 0,
172
+ totalLines: 0,
173
+ totalSize: 0,
174
+ byExtension: {},
175
+ errors: []
176
+ };
177
+
178
+ // Find all code files
179
+ const patterns = CODE_EXTENSIONS.map(ext => `**/*${ext}`);
180
+
181
+ for (const pattern of patterns) {
182
+ try {
183
+ const foundFiles = await glob(pattern, {
184
+ cwd: basePath,
185
+ absolute: true,
186
+ ignore: IGNORE_DIRS.map(dir => `**/${dir}/**`)
187
+ });
188
+
189
+ for (const filePath of foundFiles) {
190
+ if (!shouldIgnore(filePath, basePath)) {
191
+ files.push(filePath);
192
+ }
193
+ }
194
+ } catch (error) {
195
+ stats.errors.push(`Error globbing ${pattern}: ${error.message}`);
196
+ }
197
+ }
198
+
199
+ // Analyze each file
200
+ const analyzedFiles = files.map(filePath => {
201
+ const analysis = analyzeFile(filePath);
202
+ stats.totalFiles++;
203
+
204
+ if (!analysis.error) {
205
+ stats.totalLines += analysis.totalLines;
206
+ stats.totalSize += analysis.size;
207
+
208
+ const ext = analysis.extension;
209
+ if (!stats.byExtension[ext]) {
210
+ stats.byExtension[ext] = { count: 0, lines: 0 };
211
+ }
212
+ stats.byExtension[ext].count++;
213
+ stats.byExtension[ext].lines += analysis.totalLines;
214
+ } else {
215
+ stats.errors.push(`Error analyzing ${analysis.path}: ${analysis.error}`);
216
+ }
217
+
218
+ return analysis;
219
+ });
220
+
221
+ // Generate markdown content
222
+ const markdown = generateMarkdown(analyzedFiles, stats, basePath);
223
+
224
+ // Write to file
225
+ fs.writeFileSync(outputPath, markdown, 'utf-8');
226
+ }
227
+
228
+ /**
229
+ * Generate markdown documentation
230
+ */
231
+ function generateMarkdown(files, stats, basePath) {
232
+ const timestamp = new Date().toISOString();
233
+ const projectName = path.basename(basePath);
234
+
235
+ let md = `# ASL Parser Report: ${projectName}\n\n`;
236
+ md += `**Generated:** ${timestamp}\n\n`;
237
+ md += `---\n\n`;
238
+
239
+ // Summary Statistics
240
+ md += `## 📊 Summary Statistics\n\n`;
241
+ md += `- **Total Files Analyzed:** ${stats.totalFiles}\n`;
242
+ md += `- **Total Lines of Code:** ${stats.totalLines.toLocaleString()}\n`;
243
+ md += `- **Total Size:** ${(stats.totalSize / 1024).toFixed(2)} KB\n\n`;
244
+
245
+ // Files by Extension
246
+ md += `### Files by Extension\n\n`;
247
+ md += `| Extension | Count | Lines |\n`;
248
+ md += `|-----------|-------|-------|\n`;
249
+ const sortedExtensions = Object.entries(stats.byExtension)
250
+ .sort((a, b) => b[1].count - a[1].count);
251
+ for (const [ext, data] of sortedExtensions) {
252
+ md += `| ${ext || '(no extension)'} | ${data.count} | ${data.lines.toLocaleString()} |\n`;
253
+ }
254
+ md += `\n`;
255
+
256
+ // File Details
257
+ md += `## 📁 File Analysis\n\n`;
258
+
259
+ // Group files by directory
260
+ const filesByDir = {};
261
+ for (const file of files) {
262
+ if (file.error) continue;
263
+
264
+ const dir = path.dirname(file.path);
265
+ if (!filesByDir[dir]) {
266
+ filesByDir[dir] = [];
267
+ }
268
+ filesByDir[dir].push(file);
269
+ }
270
+
271
+ const sortedDirs = Object.keys(filesByDir).sort();
272
+
273
+ for (const dir of sortedDirs) {
274
+ const dirFiles = filesByDir[dir];
275
+ md += `### ${dir || '(root)'}\n\n`;
276
+
277
+ for (const file of dirFiles) {
278
+ md += `#### \`${file.fileName}\`\n\n`;
279
+ md += `- **Path:** \`${file.path}\`\n`;
280
+ md += `- **Extension:** ${file.extension || '(none)'}\n`;
281
+ md += `- **Lines:** ${file.totalLines} (${file.codeLines} code, ${file.commentLines} comments)\n`;
282
+ md += `- **Size:** ${(file.size / 1024).toFixed(2)} KB\n`;
283
+
284
+ if (file.classes.length > 0) {
285
+ md += `- **Classes:** ${file.classes.join(', ')}\n`;
286
+ }
287
+
288
+ if (file.functions.length > 0) {
289
+ md += `- **Functions:** ${file.functions.slice(0, 10).join(', ')}${file.functions.length > 10 ? ` (and ${file.functions.length - 10} more)` : ''}\n`;
290
+ }
291
+
292
+ md += `\n`;
293
+ }
294
+ }
295
+
296
+ // Errors
297
+ if (stats.errors.length > 0) {
298
+ md += `## ⚠️ Errors\n\n`;
299
+ for (const error of stats.errors) {
300
+ md += `- ${error}\n`;
301
+ }
302
+ md += `\n`;
303
+ }
304
+
305
+ // Agent Specifications
306
+ md += `---\n\n`;
307
+ md += `## 🤖 Agent Specifications\n\n`;
308
+ md += `This section contains structured information about the codebase that can be used by AI agents.\n\n`;
309
+
310
+ // Generate agent-friendly structure
311
+ md += `### Codebase Structure\n\n`;
312
+ md += `\`\`\`json\n`;
313
+ md += JSON.stringify({
314
+ projectName,
315
+ totalFiles: stats.totalFiles,
316
+ totalLines: stats.totalLines,
317
+ extensions: stats.byExtension,
318
+ structure: sortedDirs.map(dir => ({
319
+ directory: dir,
320
+ files: filesByDir[dir].map(f => ({
321
+ name: f.fileName,
322
+ path: f.path,
323
+ extension: f.extension,
324
+ lines: f.totalLines,
325
+ classes: f.classes,
326
+ functions: f.functions
327
+ }))
328
+ }))
329
+ }, null, 2);
330
+ md += `\n\`\`\`\n\n`;
331
+
332
+ return md;
333
+ }
334
+
335
+ module.exports = { parseCodebase, analyzeFile };