archicore 0.1.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 +530 -0
- package/dist/analyzers/dead-code.d.ts +95 -0
- package/dist/analyzers/dead-code.js +327 -0
- package/dist/analyzers/duplication.d.ts +90 -0
- package/dist/analyzers/duplication.js +344 -0
- package/dist/analyzers/security.d.ts +79 -0
- package/dist/analyzers/security.js +484 -0
- package/dist/architecture/index.d.ts +35 -0
- package/dist/architecture/index.js +249 -0
- package/dist/cli/commands/analyzers.d.ts +6 -0
- package/dist/cli/commands/analyzers.js +431 -0
- package/dist/cli/commands/export.d.ts +6 -0
- package/dist/cli/commands/export.js +78 -0
- package/dist/cli/commands/index.d.ts +8 -0
- package/dist/cli/commands/index.js +8 -0
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.js +140 -0
- package/dist/cli/commands/interactive.d.ts +7 -0
- package/dist/cli/commands/interactive.js +522 -0
- package/dist/cli/commands/projects.d.ts +6 -0
- package/dist/cli/commands/projects.js +249 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/ui/box.d.ts +17 -0
- package/dist/cli/ui/box.js +62 -0
- package/dist/cli/ui/colors.d.ts +49 -0
- package/dist/cli/ui/colors.js +86 -0
- package/dist/cli/ui/index.d.ts +9 -0
- package/dist/cli/ui/index.js +9 -0
- package/dist/cli/ui/prompt.d.ts +34 -0
- package/dist/cli/ui/prompt.js +122 -0
- package/dist/cli/ui/spinner.d.ts +29 -0
- package/dist/cli/ui/spinner.js +80 -0
- package/dist/cli/ui/table.d.ts +33 -0
- package/dist/cli/ui/table.js +84 -0
- package/dist/cli/utils/config.d.ts +23 -0
- package/dist/cli/utils/config.js +73 -0
- package/dist/cli/utils/index.d.ts +6 -0
- package/dist/cli/utils/index.js +6 -0
- package/dist/cli/utils/session.d.ts +27 -0
- package/dist/cli/utils/session.js +117 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +295 -0
- package/dist/code-index/ast-parser.d.ts +16 -0
- package/dist/code-index/ast-parser.js +330 -0
- package/dist/code-index/dependency-graph.d.ts +16 -0
- package/dist/code-index/dependency-graph.js +161 -0
- package/dist/code-index/index.d.ts +44 -0
- package/dist/code-index/index.js +124 -0
- package/dist/code-index/symbol-extractor.d.ts +13 -0
- package/dist/code-index/symbol-extractor.js +150 -0
- package/dist/export/index.d.ts +92 -0
- package/dist/export/index.js +676 -0
- package/dist/github/github-service.d.ts +146 -0
- package/dist/github/github-service.js +609 -0
- package/dist/impact-engine/index.d.ts +25 -0
- package/dist/impact-engine/index.js +284 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +149 -0
- package/dist/metrics/index.d.ts +136 -0
- package/dist/metrics/index.js +525 -0
- package/dist/orchestrator/deepseek-optimizer.d.ts +67 -0
- package/dist/orchestrator/deepseek-optimizer.js +320 -0
- package/dist/orchestrator/index.d.ts +34 -0
- package/dist/orchestrator/index.js +305 -0
- package/dist/pr-guardian/index.d.ts +143 -0
- package/dist/pr-guardian/index.js +553 -0
- package/dist/refactoring/index.d.ts +108 -0
- package/dist/refactoring/index.js +580 -0
- package/dist/rules-engine/index.d.ts +129 -0
- package/dist/rules-engine/index.js +482 -0
- package/dist/semantic-memory/embedding-service.d.ts +24 -0
- package/dist/semantic-memory/embedding-service.js +120 -0
- package/dist/semantic-memory/index.d.ts +45 -0
- package/dist/semantic-memory/index.js +206 -0
- package/dist/semantic-memory/vector-store.d.ts +27 -0
- package/dist/semantic-memory/vector-store.js +166 -0
- package/dist/server/index.d.ts +28 -0
- package/dist/server/index.js +141 -0
- package/dist/server/middleware/api-auth.d.ts +43 -0
- package/dist/server/middleware/api-auth.js +256 -0
- package/dist/server/routes/admin.d.ts +5 -0
- package/dist/server/routes/admin.js +123 -0
- package/dist/server/routes/api.d.ts +7 -0
- package/dist/server/routes/api.js +362 -0
- package/dist/server/routes/auth.d.ts +16 -0
- package/dist/server/routes/auth.js +191 -0
- package/dist/server/routes/developer.d.ts +8 -0
- package/dist/server/routes/developer.js +439 -0
- package/dist/server/routes/github.d.ts +7 -0
- package/dist/server/routes/github.js +495 -0
- package/dist/server/routes/upload.d.ts +7 -0
- package/dist/server/routes/upload.js +196 -0
- package/dist/server/services/api-key-service.d.ts +81 -0
- package/dist/server/services/api-key-service.js +281 -0
- package/dist/server/services/auth-service.d.ts +40 -0
- package/dist/server/services/auth-service.js +315 -0
- package/dist/server/services/project-service.d.ts +123 -0
- package/dist/server/services/project-service.js +533 -0
- package/dist/server/services/token-service.d.ts +107 -0
- package/dist/server/services/token-service.js +416 -0
- package/dist/server/services/upload-service.d.ts +93 -0
- package/dist/server/services/upload-service.js +464 -0
- package/dist/types/api.d.ts +188 -0
- package/dist/types/api.js +86 -0
- package/dist/types/github.d.ts +335 -0
- package/dist/types/github.js +5 -0
- package/dist/types/index.d.ts +265 -0
- package/dist/types/index.js +32 -0
- package/dist/types/user.d.ts +69 -0
- package/dist/types/user.js +42 -0
- package/dist/utils/file-utils.d.ts +20 -0
- package/dist/utils/file-utils.js +163 -0
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.js +41 -0
- package/dist/watcher/index.d.ts +125 -0
- package/dist/watcher/index.js +397 -0
- package/package.json +71 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ArchiCore CLI
|
|
4
|
+
*
|
|
5
|
+
* AI Software Architect - Interactive CLI
|
|
6
|
+
*/
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { AIArhitector } from './index.js';
|
|
10
|
+
import { Logger, LogLevel } from './utils/logger.js';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import ora from 'ora';
|
|
13
|
+
import { writeFile } from 'fs/promises';
|
|
14
|
+
// Import new CLI modules
|
|
15
|
+
import { registerAnalyzerCommands, registerExportCommand, startInteractiveMode, initProject, } from './cli/index.js';
|
|
16
|
+
const program = new Command();
|
|
17
|
+
program
|
|
18
|
+
.name('archicore')
|
|
19
|
+
.description('ArchiCore - AI Software Architect')
|
|
20
|
+
.version('0.1.0');
|
|
21
|
+
// Init command - initialize project in current directory
|
|
22
|
+
program
|
|
23
|
+
.command('init')
|
|
24
|
+
.description('Initialize ArchiCore in current directory')
|
|
25
|
+
.option('-n, --name <name>', 'Project name')
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
await initProject(process.cwd(), options.name);
|
|
28
|
+
});
|
|
29
|
+
// Register modular commands
|
|
30
|
+
registerAnalyzerCommands(program);
|
|
31
|
+
registerExportCommand(program);
|
|
32
|
+
// Interactive mode (default when no command)
|
|
33
|
+
program
|
|
34
|
+
.command('chat', { isDefault: false })
|
|
35
|
+
.description('Start interactive mode')
|
|
36
|
+
.action(async () => {
|
|
37
|
+
await startInteractiveMode();
|
|
38
|
+
});
|
|
39
|
+
// Legacy commands (keeping for backwards compatibility)
|
|
40
|
+
program
|
|
41
|
+
.command('index')
|
|
42
|
+
.description('Index a project (legacy)')
|
|
43
|
+
.option('-d, --dir <directory>', 'Project directory', process.cwd())
|
|
44
|
+
.option('-v, --verbose', 'Verbose output')
|
|
45
|
+
.action(async (options) => {
|
|
46
|
+
if (options.verbose) {
|
|
47
|
+
Logger.setLevel(LogLevel.DEBUG);
|
|
48
|
+
}
|
|
49
|
+
const spinner = ora('Initializing AIArhitector...').start();
|
|
50
|
+
try {
|
|
51
|
+
const ai = new AIArhitector({
|
|
52
|
+
rootDir: options.dir,
|
|
53
|
+
llm: {
|
|
54
|
+
provider: 'deepseek',
|
|
55
|
+
model: 'deepseek-chat',
|
|
56
|
+
temperature: 0.1,
|
|
57
|
+
maxTokens: 4096
|
|
58
|
+
},
|
|
59
|
+
vectorStore: {
|
|
60
|
+
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
61
|
+
apiKey: process.env.QDRANT_API_KEY,
|
|
62
|
+
collectionName: 'aiarhitector'
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
spinner.text = 'Indexing project...';
|
|
66
|
+
await ai.initialize();
|
|
67
|
+
const stats = ai.getStatistics();
|
|
68
|
+
spinner.succeed('Project indexed!');
|
|
69
|
+
console.log(chalk.green('\n📊 Statistics:\n'));
|
|
70
|
+
console.log(` Files: ${stats.codeIndex.totalFiles}`);
|
|
71
|
+
console.log(` Symbols: ${stats.codeIndex.totalSymbols}`);
|
|
72
|
+
console.log(` Nodes: ${stats.codeIndex.totalNodes}`);
|
|
73
|
+
console.log(` Edges: ${stats.codeIndex.totalEdges}`);
|
|
74
|
+
console.log(chalk.cyan('\n🔤 Symbols by kind:\n'));
|
|
75
|
+
for (const [kind, count] of Object.entries(stats.codeIndex.symbolsByKind)) {
|
|
76
|
+
console.log(` ${kind}: ${count}`);
|
|
77
|
+
}
|
|
78
|
+
const memStats = await stats.semanticMemory;
|
|
79
|
+
if (memStats) {
|
|
80
|
+
console.log(chalk.magenta('\n🧠 Semantic memory:\n'));
|
|
81
|
+
console.log(` Vectors: ${memStats.vectorsCount}`);
|
|
82
|
+
console.log(` Points: ${memStats.pointsCount}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
spinner.fail('Indexing error');
|
|
87
|
+
Logger.error('Error', error);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
program
|
|
92
|
+
.command('analyze')
|
|
93
|
+
.description('Analyze impact of changes (legacy)')
|
|
94
|
+
.requiredOption('-d, --description <desc>', 'Change description')
|
|
95
|
+
.option('--files <files>', 'Files (comma-separated)')
|
|
96
|
+
.option('--symbols <symbols>', 'Symbols (comma-separated)')
|
|
97
|
+
.option('--type <type>', 'Change type (add|modify|delete|refactor)', 'modify')
|
|
98
|
+
.option('--output <file>', 'Output file')
|
|
99
|
+
.action(async (options) => {
|
|
100
|
+
const spinner = ora('Analyzing impact...').start();
|
|
101
|
+
try {
|
|
102
|
+
const ai = new AIArhitector({
|
|
103
|
+
rootDir: process.cwd(),
|
|
104
|
+
llm: {
|
|
105
|
+
provider: 'deepseek',
|
|
106
|
+
model: 'deepseek-chat',
|
|
107
|
+
temperature: 0.1,
|
|
108
|
+
maxTokens: 4096
|
|
109
|
+
},
|
|
110
|
+
vectorStore: {
|
|
111
|
+
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
112
|
+
apiKey: process.env.QDRANT_API_KEY,
|
|
113
|
+
collectionName: 'aiarhitector'
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
spinner.text = 'Loading index...';
|
|
117
|
+
await ai.initialize();
|
|
118
|
+
const change = {
|
|
119
|
+
type: options.type,
|
|
120
|
+
description: options.description,
|
|
121
|
+
files: options.files ? options.files.split(',') : [],
|
|
122
|
+
symbols: options.symbols ? options.symbols.split(',') : []
|
|
123
|
+
};
|
|
124
|
+
spinner.text = 'Analyzing...';
|
|
125
|
+
const impact = await ai.analyzeChange(change);
|
|
126
|
+
spinner.succeed('Analysis complete!');
|
|
127
|
+
console.log(chalk.bold('\n📋 IMPACT ANALYSIS REPORT\n'));
|
|
128
|
+
console.log(chalk.yellow('⚠ AFFECTED COMPONENTS:\n'));
|
|
129
|
+
console.log(` Total: ${impact.affectedNodes.length}`);
|
|
130
|
+
const byLevel = {
|
|
131
|
+
critical: impact.affectedNodes.filter(n => n.impactLevel === 'critical'),
|
|
132
|
+
high: impact.affectedNodes.filter(n => n.impactLevel === 'high'),
|
|
133
|
+
medium: impact.affectedNodes.filter(n => n.impactLevel === 'medium'),
|
|
134
|
+
low: impact.affectedNodes.filter(n => n.impactLevel === 'low')
|
|
135
|
+
};
|
|
136
|
+
console.log(` 🔴 Critical: ${byLevel.critical.length}`);
|
|
137
|
+
console.log(` 🟠 High: ${byLevel.high.length}`);
|
|
138
|
+
console.log(` 🟡 Medium: ${byLevel.medium.length}`);
|
|
139
|
+
console.log(` 🟢 Low: ${byLevel.low.length}`);
|
|
140
|
+
if (byLevel.critical.length > 0) {
|
|
141
|
+
console.log(chalk.red('\n🔴 CRITICAL COMPONENTS:\n'));
|
|
142
|
+
for (const node of byLevel.critical.slice(0, 10)) {
|
|
143
|
+
console.log(` - ${node.name} (${node.filePath})`);
|
|
144
|
+
console.log(` ${node.reason}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
console.log(chalk.red('\n⚠ RISKS:\n'));
|
|
148
|
+
for (const risk of impact.risks) {
|
|
149
|
+
console.log(` [${risk.severity.toUpperCase()}] ${risk.description}`);
|
|
150
|
+
if (risk.mitigation) {
|
|
151
|
+
console.log(` 💡 ${risk.mitigation}`);
|
|
152
|
+
}
|
|
153
|
+
console.log('');
|
|
154
|
+
}
|
|
155
|
+
console.log(chalk.blue('\n✅ RECOMMENDATIONS:\n'));
|
|
156
|
+
for (const rec of impact.recommendations) {
|
|
157
|
+
console.log(` [${rec.priority.toUpperCase()}] ${rec.description}`);
|
|
158
|
+
if (rec.details) {
|
|
159
|
+
console.log(` ${rec.details}`);
|
|
160
|
+
}
|
|
161
|
+
console.log('');
|
|
162
|
+
}
|
|
163
|
+
if (options.output) {
|
|
164
|
+
const report = {
|
|
165
|
+
change,
|
|
166
|
+
impact,
|
|
167
|
+
timestamp: new Date().toISOString()
|
|
168
|
+
};
|
|
169
|
+
await writeFile(options.output, JSON.stringify(report, null, 2));
|
|
170
|
+
console.log(chalk.green(`\n✓ Report saved: ${options.output}`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
spinner.fail('Analysis error');
|
|
175
|
+
Logger.error('Error', error);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
program
|
|
180
|
+
.command('search')
|
|
181
|
+
.description('Semantic code search (legacy)')
|
|
182
|
+
.requiredOption('-q, --query <query>', 'Search query')
|
|
183
|
+
.option('-l, --limit <limit>', 'Number of results', '10')
|
|
184
|
+
.action(async (options) => {
|
|
185
|
+
const spinner = ora('Searching...').start();
|
|
186
|
+
try {
|
|
187
|
+
const ai = new AIArhitector({
|
|
188
|
+
rootDir: process.cwd(),
|
|
189
|
+
llm: {
|
|
190
|
+
provider: 'deepseek',
|
|
191
|
+
model: 'deepseek-chat',
|
|
192
|
+
temperature: 0.1,
|
|
193
|
+
maxTokens: 4096
|
|
194
|
+
},
|
|
195
|
+
vectorStore: {
|
|
196
|
+
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
197
|
+
apiKey: process.env.QDRANT_API_KEY,
|
|
198
|
+
collectionName: 'aiarhitector'
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
await ai.initialize();
|
|
202
|
+
spinner.text = 'Executing search...';
|
|
203
|
+
const results = await ai.searchCode(options.query, parseInt(options.limit));
|
|
204
|
+
spinner.succeed(`Found ${results.length} results`);
|
|
205
|
+
console.log(chalk.bold('\n🔍 SEARCH RESULTS:\n'));
|
|
206
|
+
for (const [index, result] of results.entries()) {
|
|
207
|
+
console.log(chalk.cyan(`${index + 1}. ${result.chunk.metadata.filePath}:${result.chunk.metadata.startLine}`));
|
|
208
|
+
console.log(` Relevance: ${(result.score * 100).toFixed(2)}%`);
|
|
209
|
+
console.log(` ${result.context}\n`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
spinner.fail('Search error');
|
|
214
|
+
Logger.error('Error', error);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
program
|
|
219
|
+
.command('ask')
|
|
220
|
+
.description('Ask about architecture (legacy)')
|
|
221
|
+
.requiredOption('-q, --question <question>', 'Question')
|
|
222
|
+
.action(async (options) => {
|
|
223
|
+
const spinner = ora('Analyzing...').start();
|
|
224
|
+
try {
|
|
225
|
+
const ai = new AIArhitector({
|
|
226
|
+
rootDir: process.cwd(),
|
|
227
|
+
llm: {
|
|
228
|
+
provider: 'deepseek',
|
|
229
|
+
model: 'deepseek-chat',
|
|
230
|
+
temperature: 0.3,
|
|
231
|
+
maxTokens: 4096
|
|
232
|
+
},
|
|
233
|
+
vectorStore: {
|
|
234
|
+
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
235
|
+
apiKey: process.env.QDRANT_API_KEY,
|
|
236
|
+
collectionName: 'aiarhitector'
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
await ai.initialize();
|
|
240
|
+
spinner.text = 'Getting AI response...';
|
|
241
|
+
const answer = await ai.askQuestion(options.question);
|
|
242
|
+
spinner.succeed('Answer received!');
|
|
243
|
+
console.log(chalk.bold('\n💬 AI ARCHITECT:\n'));
|
|
244
|
+
console.log(answer);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
spinner.fail('Error');
|
|
248
|
+
Logger.error('Error', error);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
program
|
|
253
|
+
.command('docs')
|
|
254
|
+
.description('Generate documentation (legacy)')
|
|
255
|
+
.option('-o, --output <file>', 'Output file', 'ARCHITECTURE.md')
|
|
256
|
+
.action(async (options) => {
|
|
257
|
+
const spinner = ora('Generating documentation...').start();
|
|
258
|
+
try {
|
|
259
|
+
const ai = new AIArhitector({
|
|
260
|
+
rootDir: process.cwd(),
|
|
261
|
+
llm: {
|
|
262
|
+
provider: 'deepseek',
|
|
263
|
+
model: 'deepseek-chat',
|
|
264
|
+
temperature: 0.3,
|
|
265
|
+
maxTokens: 8192
|
|
266
|
+
},
|
|
267
|
+
vectorStore: {
|
|
268
|
+
url: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
269
|
+
apiKey: process.env.QDRANT_API_KEY,
|
|
270
|
+
collectionName: 'aiarhitector'
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
await ai.initialize();
|
|
274
|
+
spinner.text = 'Generating documentation...';
|
|
275
|
+
const docs = await ai.generateDocumentation();
|
|
276
|
+
await writeFile(options.output, docs);
|
|
277
|
+
spinner.succeed(`Documentation saved: ${options.output}`);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
spinner.fail('Documentation generation error');
|
|
281
|
+
Logger.error('Error', error);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
// Handle no command - start interactive mode
|
|
286
|
+
if (process.argv.length === 2) {
|
|
287
|
+
startInteractiveMode().catch((error) => {
|
|
288
|
+
console.error('Failed to start interactive mode:', error);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
program.parse();
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Parser from 'tree-sitter';
|
|
2
|
+
import { ASTNode, Location } from '../types/index.js';
|
|
3
|
+
export declare class ASTParser {
|
|
4
|
+
private parsers;
|
|
5
|
+
constructor();
|
|
6
|
+
private initializeParsers;
|
|
7
|
+
parseFile(filePath: string): Promise<ASTNode | null>;
|
|
8
|
+
private parseWithRegex;
|
|
9
|
+
private inferTypeFromPattern;
|
|
10
|
+
private extractVueScript;
|
|
11
|
+
parseProject(rootDir: string): Promise<Map<string, ASTNode>>;
|
|
12
|
+
private convertToASTNode;
|
|
13
|
+
private extractName;
|
|
14
|
+
extractLocation(node: Parser.SyntaxNode, filePath: string): Location;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ast-parser.d.ts.map
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import Parser from 'tree-sitter';
|
|
2
|
+
import TypeScript from 'tree-sitter-typescript';
|
|
3
|
+
import JavaScript from 'tree-sitter-javascript';
|
|
4
|
+
import Python from 'tree-sitter-python';
|
|
5
|
+
import { FileUtils } from '../utils/file-utils.js';
|
|
6
|
+
import { Logger } from '../utils/logger.js';
|
|
7
|
+
// Tree-sitter has a ~32KB limit for parsing
|
|
8
|
+
const MAX_TREE_SITTER_SIZE = 30000;
|
|
9
|
+
export class ASTParser {
|
|
10
|
+
parsers;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.parsers = new Map();
|
|
13
|
+
this.initializeParsers();
|
|
14
|
+
}
|
|
15
|
+
initializeParsers() {
|
|
16
|
+
const tsParser = new Parser();
|
|
17
|
+
tsParser.setLanguage(TypeScript.typescript);
|
|
18
|
+
this.parsers.set('typescript', tsParser);
|
|
19
|
+
const jsParser = new Parser();
|
|
20
|
+
jsParser.setLanguage(JavaScript);
|
|
21
|
+
this.parsers.set('javascript', jsParser);
|
|
22
|
+
const pyParser = new Parser();
|
|
23
|
+
pyParser.setLanguage(Python);
|
|
24
|
+
this.parsers.set('python', pyParser);
|
|
25
|
+
Logger.debug('AST parsers initialized');
|
|
26
|
+
}
|
|
27
|
+
async parseFile(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
let content = await FileUtils.readFileContent(filePath);
|
|
30
|
+
let language = FileUtils.getLanguageFromExtension(filePath);
|
|
31
|
+
// Vue SFC: extract <script> section and parse as TS/JS
|
|
32
|
+
if (language === 'vue') {
|
|
33
|
+
const scriptResult = this.extractVueScript(content);
|
|
34
|
+
if (!scriptResult) {
|
|
35
|
+
return null; // No script section found
|
|
36
|
+
}
|
|
37
|
+
content = scriptResult.content;
|
|
38
|
+
language = scriptResult.isTypeScript ? 'typescript' : 'javascript';
|
|
39
|
+
}
|
|
40
|
+
// Validate content before parsing
|
|
41
|
+
if (!content || typeof content !== 'string' || !content.trim()) {
|
|
42
|
+
Logger.warn(`Empty or invalid content in: ${filePath}`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
// For large files, use regex-based fallback
|
|
46
|
+
if (content.length > MAX_TREE_SITTER_SIZE) {
|
|
47
|
+
Logger.debug(`Large file (${content.length} bytes), using regex fallback: ${filePath}`);
|
|
48
|
+
return this.parseWithRegex(content, filePath, language);
|
|
49
|
+
}
|
|
50
|
+
const parser = this.parsers.get(language);
|
|
51
|
+
if (!parser) {
|
|
52
|
+
// Use regex fallback for unsupported languages
|
|
53
|
+
return this.parseWithRegex(content, filePath, language);
|
|
54
|
+
}
|
|
55
|
+
const tree = parser.parse(content);
|
|
56
|
+
return this.convertToASTNode(tree.rootNode, filePath);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// Try regex fallback on parse error
|
|
60
|
+
try {
|
|
61
|
+
const content = await FileUtils.readFileContent(filePath);
|
|
62
|
+
const language = FileUtils.getLanguageFromExtension(filePath);
|
|
63
|
+
Logger.debug(`Tree-sitter failed, using regex fallback: ${filePath}`);
|
|
64
|
+
return this.parseWithRegex(content, filePath, language);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
Logger.error(`Failed to parse ${filePath}`, error);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Regex-based fallback for large files or unsupported languages
|
|
73
|
+
parseWithRegex(content, filePath, language) {
|
|
74
|
+
const children = [];
|
|
75
|
+
const lines = content.split('\n');
|
|
76
|
+
// Patterns for different languages
|
|
77
|
+
const patterns = {
|
|
78
|
+
javascript: [
|
|
79
|
+
/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
80
|
+
/^(?:export\s+)?class\s+(\w+)/,
|
|
81
|
+
/^(?:export\s+)?const\s+(\w+)\s*=/,
|
|
82
|
+
/^(?:export\s+)?let\s+(\w+)\s*=/,
|
|
83
|
+
/(\w+)\s*[=:]\s*(?:async\s+)?\([^)]*\)\s*(?:=>|{)/,
|
|
84
|
+
/(\w+)\s*\([^)]*\)\s*{/
|
|
85
|
+
],
|
|
86
|
+
typescript: [
|
|
87
|
+
/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
88
|
+
/^(?:export\s+)?class\s+(\w+)/,
|
|
89
|
+
/^(?:export\s+)?interface\s+(\w+)/,
|
|
90
|
+
/^(?:export\s+)?type\s+(\w+)\s*=/,
|
|
91
|
+
/^(?:export\s+)?const\s+(\w+)\s*[=:]/,
|
|
92
|
+
/^(?:export\s+)?enum\s+(\w+)/,
|
|
93
|
+
/(\w+)\s*[=:]\s*(?:async\s+)?\([^)]*\)\s*(?:=>|{)/,
|
|
94
|
+
/(\w+)\s*\([^)]*\)\s*(?::\s*\w+)?\s*{/
|
|
95
|
+
],
|
|
96
|
+
python: [
|
|
97
|
+
/^(?:async\s+)?def\s+(\w+)/,
|
|
98
|
+
/^class\s+(\w+)/
|
|
99
|
+
],
|
|
100
|
+
vue: [
|
|
101
|
+
/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
102
|
+
/^(?:export\s+)?const\s+(\w+)\s*=/,
|
|
103
|
+
/(\w+)\s*[=:]\s*(?:async\s+)?\([^)]*\)\s*(?:=>|{)/,
|
|
104
|
+
/(\w+)\s*\([^)]*\)\s*{/
|
|
105
|
+
],
|
|
106
|
+
go: [
|
|
107
|
+
/^func\s+(?:\([^)]+\)\s+)?(\w+)/,
|
|
108
|
+
/^type\s+(\w+)\s+(?:struct|interface)/
|
|
109
|
+
],
|
|
110
|
+
rust: [
|
|
111
|
+
/^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/,
|
|
112
|
+
/^(?:pub\s+)?struct\s+(\w+)/,
|
|
113
|
+
/^(?:pub\s+)?enum\s+(\w+)/,
|
|
114
|
+
/^(?:pub\s+)?trait\s+(\w+)/,
|
|
115
|
+
/^impl(?:<[^>]+>)?\s+(\w+)/
|
|
116
|
+
],
|
|
117
|
+
java: [
|
|
118
|
+
/^(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?class\s+(\w+)/,
|
|
119
|
+
/^(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?interface\s+(\w+)/,
|
|
120
|
+
/^(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:\w+\s+)+(\w+)\s*\(/
|
|
121
|
+
],
|
|
122
|
+
php: [
|
|
123
|
+
/^(?:public|private|protected)?\s*(?:static\s+)?function\s+(\w+)/,
|
|
124
|
+
/^class\s+(\w+)/,
|
|
125
|
+
/^interface\s+(\w+)/,
|
|
126
|
+
/^trait\s+(\w+)/
|
|
127
|
+
],
|
|
128
|
+
ruby: [
|
|
129
|
+
/^def\s+(\w+)/,
|
|
130
|
+
/^class\s+(\w+)/,
|
|
131
|
+
/^module\s+(\w+)/
|
|
132
|
+
],
|
|
133
|
+
csharp: [
|
|
134
|
+
/^(?:public|private|protected|internal)?\s*(?:static\s+)?(?:partial\s+)?class\s+(\w+)/,
|
|
135
|
+
/^(?:public|private|protected|internal)?\s*interface\s+(\w+)/,
|
|
136
|
+
/^(?:public|private|protected|internal)?\s*(?:static\s+)?(?:async\s+)?(?:\w+\s+)+(\w+)\s*\(/
|
|
137
|
+
],
|
|
138
|
+
cpp: [
|
|
139
|
+
/^(?:class|struct)\s+(\w+)/,
|
|
140
|
+
/^(?:\w+\s+)*(\w+)\s*\([^)]*\)\s*(?:const)?\s*(?:override)?\s*{/,
|
|
141
|
+
/^namespace\s+(\w+)/
|
|
142
|
+
],
|
|
143
|
+
c: [
|
|
144
|
+
/^(?:\w+\s+)*(\w+)\s*\([^)]*\)\s*{/,
|
|
145
|
+
/^struct\s+(\w+)/,
|
|
146
|
+
/^typedef\s+.*\s+(\w+)\s*;/
|
|
147
|
+
]
|
|
148
|
+
};
|
|
149
|
+
const langPatterns = patterns[language] || patterns.javascript;
|
|
150
|
+
lines.forEach((line, index) => {
|
|
151
|
+
const trimmedLine = line.trim();
|
|
152
|
+
for (const pattern of langPatterns) {
|
|
153
|
+
const match = trimmedLine.match(pattern);
|
|
154
|
+
if (match && match[1]) {
|
|
155
|
+
const name = match[1];
|
|
156
|
+
// Skip common false positives
|
|
157
|
+
if (['if', 'else', 'for', 'while', 'switch', 'catch', 'try', 'return', 'new', 'throw'].includes(name)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const type = this.inferTypeFromPattern(pattern, language);
|
|
161
|
+
children.push({
|
|
162
|
+
id: `${filePath}:${name}:${index}`,
|
|
163
|
+
type,
|
|
164
|
+
name,
|
|
165
|
+
filePath,
|
|
166
|
+
startLine: index,
|
|
167
|
+
endLine: index,
|
|
168
|
+
children: [],
|
|
169
|
+
metadata: {
|
|
170
|
+
text: trimmedLine.substring(0, 200),
|
|
171
|
+
hasErrors: false,
|
|
172
|
+
regexParsed: true
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
break; // One match per line
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
id: `${filePath}:0:0`,
|
|
181
|
+
type: 'program',
|
|
182
|
+
name: filePath.split(/[/\\]/).pop() || '',
|
|
183
|
+
filePath,
|
|
184
|
+
startLine: 0,
|
|
185
|
+
endLine: lines.length,
|
|
186
|
+
children,
|
|
187
|
+
metadata: {
|
|
188
|
+
text: `File: ${filePath}`,
|
|
189
|
+
hasErrors: false,
|
|
190
|
+
regexParsed: true,
|
|
191
|
+
symbolCount: children.length
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
inferTypeFromPattern(pattern, _language) {
|
|
196
|
+
const patternStr = pattern.source;
|
|
197
|
+
if (patternStr.includes('class'))
|
|
198
|
+
return 'class_declaration';
|
|
199
|
+
if (patternStr.includes('interface'))
|
|
200
|
+
return 'interface_declaration';
|
|
201
|
+
if (patternStr.includes('function') || patternStr.includes('def') || patternStr.includes('fn'))
|
|
202
|
+
return 'function_declaration';
|
|
203
|
+
if (patternStr.includes('type') || patternStr.includes('enum'))
|
|
204
|
+
return 'type_alias_declaration';
|
|
205
|
+
if (patternStr.includes('struct'))
|
|
206
|
+
return 'struct_declaration';
|
|
207
|
+
if (patternStr.includes('trait'))
|
|
208
|
+
return 'trait_declaration';
|
|
209
|
+
if (patternStr.includes('const') || patternStr.includes('let'))
|
|
210
|
+
return 'variable_declaration';
|
|
211
|
+
if (patternStr.includes('module') || patternStr.includes('namespace'))
|
|
212
|
+
return 'module_declaration';
|
|
213
|
+
if (patternStr.includes('impl'))
|
|
214
|
+
return 'impl_declaration';
|
|
215
|
+
return 'function_declaration';
|
|
216
|
+
}
|
|
217
|
+
extractVueScript(content) {
|
|
218
|
+
// Find script blocks - there may be multiple (setup + regular)
|
|
219
|
+
// We want the main script block, not script setup (which uses different syntax)
|
|
220
|
+
const scriptRegex = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
221
|
+
let match;
|
|
222
|
+
let bestMatch = null;
|
|
223
|
+
while ((match = scriptRegex.exec(content)) !== null) {
|
|
224
|
+
const attrs = match[1] || '';
|
|
225
|
+
const scriptContent = match[2] || '';
|
|
226
|
+
// Skip empty scripts
|
|
227
|
+
if (!scriptContent.trim())
|
|
228
|
+
continue;
|
|
229
|
+
// Prefer non-setup script, but take setup if it's the only one
|
|
230
|
+
const isSetup = /\bsetup\b/i.test(attrs);
|
|
231
|
+
if (!bestMatch || !isSetup) {
|
|
232
|
+
bestMatch = { attrs, content: scriptContent };
|
|
233
|
+
// If this is a regular (non-setup) script with content, use it
|
|
234
|
+
if (!isSetup)
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!bestMatch || !bestMatch.content.trim()) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
// Check if TypeScript
|
|
242
|
+
const isTypeScript = /lang\s*=\s*["']ts["']/.test(bestMatch.attrs) ||
|
|
243
|
+
/lang\s*=\s*["']typescript["']/.test(bestMatch.attrs);
|
|
244
|
+
return {
|
|
245
|
+
content: bestMatch.content.trim(),
|
|
246
|
+
isTypeScript
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
async parseProject(rootDir) {
|
|
250
|
+
const files = await FileUtils.getAllFiles(rootDir);
|
|
251
|
+
const asts = new Map();
|
|
252
|
+
Logger.progress(`Parsing ${files.length} files...`);
|
|
253
|
+
for (const file of files) {
|
|
254
|
+
const ast = await this.parseFile(file);
|
|
255
|
+
if (ast) {
|
|
256
|
+
asts.set(file, ast);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
Logger.success(`Parsed ${asts.size} files successfully`);
|
|
260
|
+
return asts;
|
|
261
|
+
}
|
|
262
|
+
convertToASTNode(node, filePath) {
|
|
263
|
+
const children = [];
|
|
264
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
265
|
+
const child = node.child(i);
|
|
266
|
+
if (child) {
|
|
267
|
+
children.push(this.convertToASTNode(child, filePath));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
id: `${filePath}:${node.startPosition.row}:${node.startPosition.column}`,
|
|
272
|
+
type: node.type,
|
|
273
|
+
name: this.extractName(node),
|
|
274
|
+
filePath,
|
|
275
|
+
startLine: node.startPosition.row,
|
|
276
|
+
endLine: node.endPosition.row,
|
|
277
|
+
children,
|
|
278
|
+
metadata: {
|
|
279
|
+
text: node.text.length > 200 ? node.text.substring(0, 200) + '...' : node.text,
|
|
280
|
+
hasErrors: node.hasError
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
extractName(node) {
|
|
285
|
+
// Try field name first (works for most languages)
|
|
286
|
+
const nameNode = node.childForFieldName('name');
|
|
287
|
+
if (nameNode) {
|
|
288
|
+
return nameNode.text;
|
|
289
|
+
}
|
|
290
|
+
// Python: decorated_definition has the actual definition as child
|
|
291
|
+
if (node.type === 'decorated_definition') {
|
|
292
|
+
const defNode = node.children.find(c => c.type === 'function_definition' || c.type === 'class_definition');
|
|
293
|
+
if (defNode) {
|
|
294
|
+
const innerName = defNode.childForFieldName('name');
|
|
295
|
+
if (innerName)
|
|
296
|
+
return innerName.text;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const interestingTypes = [
|
|
300
|
+
// JS/TS
|
|
301
|
+
'function_declaration',
|
|
302
|
+
'class_declaration',
|
|
303
|
+
'interface_declaration',
|
|
304
|
+
'type_alias_declaration',
|
|
305
|
+
'variable_declaration',
|
|
306
|
+
// Python
|
|
307
|
+
'function_definition',
|
|
308
|
+
'class_definition',
|
|
309
|
+
'async_function_definition'
|
|
310
|
+
];
|
|
311
|
+
if (interestingTypes.includes(node.type)) {
|
|
312
|
+
// Try to extract name from text
|
|
313
|
+
const match = node.text.match(/(?:def|class|function|async\s+def)\s+(\w+)/);
|
|
314
|
+
if (match)
|
|
315
|
+
return match[1];
|
|
316
|
+
return node.text.split(/[\s({:]/)[1] || 'anonymous';
|
|
317
|
+
}
|
|
318
|
+
return '';
|
|
319
|
+
}
|
|
320
|
+
extractLocation(node, filePath) {
|
|
321
|
+
return {
|
|
322
|
+
filePath,
|
|
323
|
+
startLine: node.startPosition.row,
|
|
324
|
+
endLine: node.endPosition.row,
|
|
325
|
+
startColumn: node.startPosition.column,
|
|
326
|
+
endColumn: node.endPosition.column
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=ast-parser.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DependencyGraph, Symbol, ASTNode } from '../types/index.js';
|
|
2
|
+
export declare class DependencyGraphBuilder {
|
|
3
|
+
buildGraph(symbols: Map<string, Symbol>, asts: Map<string, ASTNode>): DependencyGraph;
|
|
4
|
+
private buildNodes;
|
|
5
|
+
private buildEdges;
|
|
6
|
+
private addEdge;
|
|
7
|
+
private findSymbolsBySource;
|
|
8
|
+
private findSymbolAtLocation;
|
|
9
|
+
private getFileName;
|
|
10
|
+
private estimateComplexity;
|
|
11
|
+
private countEdges;
|
|
12
|
+
findDependents(graph: DependencyGraph, nodeId: string): Set<string>;
|
|
13
|
+
findDependencies(graph: DependencyGraph, nodeId: string): Set<string>;
|
|
14
|
+
getTransitiveDependencies(graph: DependencyGraph, nodeId: string, maxDepth?: number): Set<string>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=dependency-graph.d.ts.map
|