skannr 0.1.2 β†’ 0.1.4

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.
Files changed (88) hide show
  1. package/.eslintrc.js +22 -22
  2. package/README.md +260 -151
  3. package/dist/agent-cli.js +25 -25
  4. package/dist/agent.js +4 -4
  5. package/dist/blast-radius.d.ts +80 -0
  6. package/dist/blast-radius.d.ts.map +1 -0
  7. package/dist/blast-radius.js +545 -0
  8. package/dist/blast-radius.js.map +1 -0
  9. package/dist/cli.js +55 -17
  10. package/dist/cli.js.map +1 -1
  11. package/dist/mcp-server.d.ts.map +1 -1
  12. package/dist/mcp-server.js +98 -24
  13. package/dist/mcp-server.js.map +1 -1
  14. package/dist/telemetry.js +12 -12
  15. package/gemini-extension/GEMINI.md +26 -26
  16. package/gemini-extension/gemini-extension.json +12 -12
  17. package/gemini-extension/package.json +14 -14
  18. package/gemini-extension/src/server.ts +69 -69
  19. package/gemini-extension/tsconfig.json +14 -14
  20. package/jest.config.js +4 -4
  21. package/package.json +54 -53
  22. package/src/agent-cli.ts +397 -397
  23. package/src/agent.ts +344 -344
  24. package/src/benchmark.ts +389 -389
  25. package/src/blast-radius.ts +661 -0
  26. package/src/cache.ts +317 -317
  27. package/src/cli.ts +461 -415
  28. package/src/config.ts +22 -22
  29. package/src/formatter.ts +105 -105
  30. package/src/index.ts +223 -223
  31. package/src/languages/GenericAdapter.ts +44 -44
  32. package/src/languages/LanguageAdapter.ts +21 -21
  33. package/src/languages/PythonAdapter.ts +285 -285
  34. package/src/languages/TypeScriptAdapter.ts +453 -453
  35. package/src/languages/registry.ts +49 -49
  36. package/src/mapper.ts +455 -455
  37. package/src/mcp-cli.ts +8 -8
  38. package/src/mcp-server.ts +151 -103
  39. package/src/ranker-enhanced.ts +514 -514
  40. package/src/ranker.ts +110 -110
  41. package/src/scanner.ts +226 -226
  42. package/src/skeletonizer.ts +22 -22
  43. package/src/telemetry.ts +146 -146
  44. package/src/tokenizer.ts +20 -20
  45. package/src/types.ts +92 -92
  46. package/src/watcher.ts +119 -119
  47. package/src/why.ts +88 -88
  48. package/tests/agent.tools.test.ts +81 -81
  49. package/tests/benchmark.test.ts +30 -30
  50. package/tests/blast-radius.test.ts +290 -0
  51. package/tests/fixtures/sample.py +17 -17
  52. package/tests/fixtures/sample.ts +13 -13
  53. package/tests/fixtures/src/api/routes.ts +1 -1
  54. package/tests/fixtures/src/auth/permission.ts +3 -3
  55. package/tests/python-adapter.test.ts +31 -31
  56. package/tests/ranker-enhanced.test.ts +70 -70
  57. package/tests/ranker.test.ts +79 -79
  58. package/tests/scanner.scope.test.ts +66 -66
  59. package/tests/setup-fixtures.js +28 -28
  60. package/tests/skeletonizer.test.ts +149 -149
  61. package/tsconfig.json +21 -21
  62. package/uca-landing/index.html +17 -17
  63. package/uca-landing/package.json +23 -23
  64. package/uca-landing/postcss.config.js +6 -6
  65. package/uca-landing/src/App.jsx +43 -43
  66. package/uca-landing/src/components/AgentMode.jsx +45 -45
  67. package/uca-landing/src/components/CliReference.jsx +58 -58
  68. package/uca-landing/src/components/Features.jsx +82 -82
  69. package/uca-landing/src/components/Footer.jsx +35 -35
  70. package/uca-landing/src/components/Hero.jsx +125 -125
  71. package/uca-landing/src/components/HowItWorks.jsx +60 -60
  72. package/uca-landing/src/components/Install.jsx +103 -103
  73. package/uca-landing/src/components/LanguageSupport.jsx +63 -63
  74. package/uca-landing/src/components/Navbar.jsx +86 -86
  75. package/uca-landing/src/components/Problem.jsx +51 -51
  76. package/uca-landing/src/components/Reveal.jsx +40 -40
  77. package/uca-landing/src/components/WorksWith.jsx +59 -59
  78. package/uca-landing/src/hooks/useScrollNav.js +13 -13
  79. package/uca-landing/src/hooks/useTypewriter.js +41 -41
  80. package/uca-landing/src/index.css +13 -13
  81. package/uca-landing/src/main.jsx +10 -10
  82. package/uca-landing/tailwind.config.js +68 -68
  83. package/uca-landing/vercel.json +3 -3
  84. package/uca-landing/vite.config.js +6 -6
  85. package/dist/rocket-chat-scope.d.ts +0 -7
  86. package/dist/rocket-chat-scope.d.ts.map +0 -1
  87. package/dist/rocket-chat-scope.js +0 -95
  88. package/dist/rocket-chat-scope.js.map +0 -1
package/src/agent-cli.ts CHANGED
@@ -1,397 +1,397 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Agent CLI - Interactive code analysis with Gemini AI
5
- * This demonstrates the agentic code analysis workflow
6
- */
7
-
8
- import { createCodeAnalysisAgent } from './agent';
9
- import { analyzeProject } from './index';
10
- import type { AnalysisResult } from './types';
11
- import * as readline from 'readline';
12
- import * as path from 'path';
13
-
14
- interface AgentCliArgs {
15
- root: string;
16
- question: string;
17
- limit: number;
18
- apiKey?: string;
19
- model?: string;
20
- interactive?: boolean;
21
- }
22
-
23
- interface InteractiveState {
24
- result: AnalysisResult;
25
- skeletonContext: string;
26
- }
27
-
28
- function buildSkeletonContext(result: AnalysisResult, includeScores: boolean): string {
29
- return result.files
30
- .map((file) => {
31
- if (includeScores) {
32
- return `=== ${file.path} (score: ${file.score.toFixed(2)}) ===\n${file.skeleton}`;
33
- }
34
- return `=== ${file.path} ===\n${file.skeleton}`;
35
- })
36
- .join('\n\n');
37
- }
38
-
39
- function printEvidence(result: AnalysisResult): void {
40
- console.log('πŸ“š Retrieval Evidence:');
41
- for (const file of result.files.slice(0, 10)) {
42
- console.log(`- ${file.path} (score: ${file.score.toFixed(2)})`);
43
- }
44
- console.log('');
45
- }
46
-
47
- function printInteractiveHelp(): void {
48
- console.log('Commands:');
49
- console.log(' /help Show this help');
50
- console.log(' /files List currently retrieved files');
51
- console.log(' /symbols <query> Search symbols in loaded mapping');
52
- console.log(' /symbol <symbolId> Show full implementation for a symbol');
53
- console.log(' /deps <filePath> Show imports and symbols for a file');
54
- console.log(' /refresh Re-analyze project and refresh context');
55
- console.log(' /stats Show mapping statistics');
56
- console.log(' /exit Quit interactive mode');
57
- console.log('');
58
- }
59
-
60
- /**
61
- * Parse command line arguments
62
- */
63
- function parseArgs(): AgentCliArgs | null {
64
- const args = process.argv.slice(2);
65
-
66
- let root = process.cwd();
67
- let question = '';
68
- let limit = 10;
69
- let apiKey = process.env.GEMINI_API_KEY;
70
- let model = 'gemini-2.0-flash-exp';
71
- let interactive = false;
72
-
73
- for (let i = 0; i < args.length; i++) {
74
- const arg = args[i];
75
-
76
- if (arg === '--root' && i + 1 < args.length) {
77
- root = args[i + 1];
78
- i++;
79
- } else if (arg === '--question' && i + 1 < args.length) {
80
- question = args[i + 1];
81
- i++;
82
- } else if (arg === '--limit' && i + 1 < args.length) {
83
- limit = parseInt(args[i + 1], 10);
84
- if (isNaN(limit) || limit < 1) {
85
- console.error(' βœ— --limit must be a positive number');
86
- return null;
87
- }
88
- i++;
89
- } else if (arg === '--api-key' && i + 1 < args.length) {
90
- apiKey = args[i + 1];
91
- i++;
92
- } else if (arg === '--model' && i + 1 < args.length) {
93
- model = args[i + 1];
94
- i++;
95
- } else if (arg === '--interactive' || arg === '-i') {
96
- interactive = true;
97
- } else if (arg === '--help' || arg === '-h') {
98
- printHelp();
99
- return null;
100
- } else {
101
- console.error(` βœ— Unknown argument: ${arg}`);
102
- printHelp();
103
- return null;
104
- }
105
- }
106
-
107
- if (!apiKey) {
108
- console.error('');
109
- console.error(' βœ— No Gemini API key. Set GEMINI_API_KEY or pass --api-key <key>.');
110
- console.error('');
111
- return null;
112
- }
113
-
114
- if (!question && !interactive) {
115
- console.error('');
116
- console.error(' βœ— Provide --question "<text>" or use --interactive for chat mode.');
117
- console.error('');
118
- printHelp();
119
- return null;
120
- }
121
-
122
- return { root, question, limit, apiKey, model, interactive };
123
- }
124
-
125
- /**
126
- * Print help message
127
- */
128
- function printHelp(): void {
129
- console.log(`
130
- skannr-agent β€” AI-powered code analysis (Gemini)
131
-
132
- Usage:
133
- skannr-agent --question "<text>" [options]
134
- skannr-agent --interactive [options]
135
-
136
- Options:
137
- --root <path> Project root directory (default: current directory)
138
- --question "<text>" Question to analyze (required unless --interactive)
139
- --limit <number> Number of top files to analyze (default: 10)
140
- --api-key <key> Gemini API key (or set GEMINI_API_KEY env var)
141
- --model <name> Gemini model name (default: gemini-2.0-flash-exp)
142
- --interactive, -i Start interactive chat mode
143
- --help, -h Show this help message
144
-
145
- Examples:
146
- skannr-agent --question "How does authentication work?"
147
- skannr-agent --interactive --root ./my-project
148
- skannr-agent --question "Find all API endpoints" --limit 15
149
-
150
- Environment variables:
151
- GEMINI_API_KEY Gemini API key (alternative to --api-key)
152
-
153
- Docs: https://skannr-ten.vercel.app
154
- `);
155
- }
156
-
157
- /**
158
- * Run single question mode
159
- */
160
- async function runSingleQuestion(args: AgentCliArgs): Promise<void> {
161
- console.log('πŸ” Analyzing codebase...\n');
162
-
163
- // Step 1: Generate skeletons with mapping
164
- const result = await analyzeProject({
165
- root: args.root,
166
- question: args.question,
167
- limit: args.limit,
168
- generateMapping: true,
169
- });
170
-
171
- if (result.files.length === 0) {
172
- console.error('');
173
- console.error(' βœ— No files found to analyze.');
174
- console.error('');
175
- console.error(' Possible causes:');
176
- console.error(' Β· --root is wrong or the tree has no matching source files');
177
- console.error(' Β· Files are excluded by config or default excludes');
178
- console.error('');
179
- console.error(' Try: skannr-agent --question "..." --root .');
180
- console.error('');
181
- return;
182
- }
183
-
184
- console.log(`πŸ“Š Found ${result.files.length} relevant files\n`);
185
-
186
- // Prepare skeleton context
187
- const skeletonContext = buildSkeletonContext(result, true);
188
-
189
- // Step 2: Create agent and load mapping
190
- const agent = createCodeAnalysisAgent(args.apiKey!, args.model);
191
- const mappingPath = path.join(result.root, 'code-analyzer.mapping.json');
192
- agent.loadMapping(mappingPath);
193
-
194
- const stats = agent.getStats();
195
- console.log(`πŸ“ Loaded mapping: ${stats.totalFiles} files, ${stats.totalSymbols} symbols\n`);
196
- console.log(`πŸ€– Agent is analyzing...\n`);
197
-
198
- // Step 3: Send to agent
199
- const response = await agent.chat(args.question, skeletonContext);
200
-
201
- console.log('πŸ“‹ Agent Response:\n');
202
- console.log(response);
203
- console.log('\n');
204
- printEvidence(result);
205
- }
206
-
207
- /**
208
- * Run interactive mode
209
- */
210
- async function runInteractive(args: AgentCliArgs): Promise<void> {
211
- console.log('πŸš€ Starting interactive code analysis agent...\n');
212
- console.log('First, let me analyze your codebase.\n');
213
-
214
- const buildState = async (): Promise<InteractiveState> => {
215
- const result = await analyzeProject({
216
- root: args.root,
217
- question: 'code structure and organization',
218
- limit: args.limit,
219
- generateMapping: true,
220
- });
221
-
222
- if (result.files.length === 0) {
223
- return {
224
- result,
225
- skeletonContext: '',
226
- };
227
- }
228
-
229
- return {
230
- result,
231
- skeletonContext: buildSkeletonContext(result, false),
232
- };
233
- };
234
-
235
- // Step 1: Generate skeletons with mapping
236
- let state = await buildState();
237
-
238
- if (state.result.files.length === 0) {
239
- console.log('No files found for analysis.');
240
- return;
241
- }
242
-
243
- console.log(`πŸ“Š Analyzed ${state.result.files.length} files\n`);
244
-
245
- // Step 2: Create agent and load mapping
246
- const agent = createCodeAnalysisAgent(args.apiKey!, args.model);
247
- const mappingPath = path.join(state.result.root, 'code-analyzer.mapping.json');
248
- agent.loadMapping(mappingPath);
249
-
250
- const stats = agent.getStats();
251
- console.log(`πŸ“ Mapping: ${stats.totalFiles} files, ${stats.totalSymbols} symbols\n`);
252
- console.log('πŸ’¬ Ready for questions! Type /help for commands.\n');
253
-
254
- // Step 3: Interactive loop
255
- const rl = readline.createInterface({
256
- input: process.stdin,
257
- output: process.stdout,
258
- });
259
-
260
- const askQuestion = () => {
261
- rl.question('You: ', async (input) => {
262
- const question = input.trim();
263
-
264
- if (question.toLowerCase() === 'exit' || question.toLowerCase() === '/exit') {
265
- console.log('\nπŸ‘‹ Goodbye!');
266
- rl.close();
267
- return;
268
- }
269
-
270
- if (question.toLowerCase() === 'stats' || question.toLowerCase() === '/stats') {
271
- console.log('\nπŸ“Š Statistics:');
272
- console.log(JSON.stringify(agent.getStats(), null, 2));
273
- console.log('');
274
- askQuestion();
275
- return;
276
- }
277
-
278
- if (question.toLowerCase() === '/help') {
279
- console.log('');
280
- printInteractiveHelp();
281
- askQuestion();
282
- return;
283
- }
284
-
285
- if (question.toLowerCase() === '/files') {
286
- console.log('');
287
- printEvidence(state.result);
288
- askQuestion();
289
- return;
290
- }
291
-
292
- if (question.toLowerCase() === '/refresh') {
293
- console.log('\nπŸ”„ Refreshing project analysis...');
294
- state = await buildState();
295
- const refreshedMappingPath = path.join(state.result.root, 'code-analyzer.mapping.json');
296
- agent.loadMapping(refreshedMappingPath);
297
- console.log(`βœ… Refreshed ${state.result.files.length} files\n`);
298
- askQuestion();
299
- return;
300
- }
301
-
302
- if (question.startsWith('/symbols ')) {
303
- const query = question.slice('/symbols '.length).trim();
304
- if (!query) {
305
- console.log('\nUsage: /symbols <query>\n');
306
- askQuestion();
307
- return;
308
- }
309
- const symbols = agent.searchSymbols(query);
310
- console.log('\nπŸ”Ž Symbol Search Result:');
311
- console.log(JSON.stringify(symbols, null, 2));
312
- console.log('');
313
- askQuestion();
314
- return;
315
- }
316
-
317
- if (question.startsWith('/symbol ')) {
318
- const symbolId = question.slice('/symbol '.length).trim();
319
- if (!symbolId) {
320
- console.log('\nUsage: /symbol <symbolId>\n');
321
- askQuestion();
322
- return;
323
- }
324
- const symbol = agent.getSymbolDetails(symbolId);
325
- console.log('\n🧩 Symbol Details:');
326
- console.log(JSON.stringify(symbol, null, 2));
327
- console.log('');
328
- askQuestion();
329
- return;
330
- }
331
-
332
- if (question.startsWith('/deps ')) {
333
- const filePath = question.slice('/deps '.length).trim();
334
- if (!filePath) {
335
- console.log('\nUsage: /deps <filePath>\n');
336
- askQuestion();
337
- return;
338
- }
339
- const deps = agent.analyzeFileDependencies(filePath);
340
- console.log('\nπŸ•ΈοΈ File Dependencies:');
341
- console.log(JSON.stringify(deps, null, 2));
342
- console.log('');
343
- askQuestion();
344
- return;
345
- }
346
-
347
- if (!question) {
348
- askQuestion();
349
- return;
350
- }
351
-
352
- console.log('\nπŸ€– Agent: ');
353
-
354
- try {
355
- const response = await agent.chat(question, state.skeletonContext);
356
- console.log(response);
357
- console.log('');
358
- printEvidence(state.result);
359
- } catch (error) {
360
- console.error('Error:', error instanceof Error ? error.message : error);
361
- }
362
-
363
- console.log('');
364
- askQuestion();
365
- });
366
- };
367
-
368
- askQuestion();
369
- }
370
-
371
- /**
372
- * Main CLI function
373
- */
374
- async function main(): Promise<void> {
375
- const args = parseArgs();
376
-
377
- if (!args) {
378
- process.exit(1);
379
- }
380
-
381
- try {
382
- if (args.interactive) {
383
- await runInteractive(args);
384
- } else {
385
- await runSingleQuestion(args);
386
- }
387
- } catch (error) {
388
- console.error('Error:', error instanceof Error ? error.message : error);
389
- process.exit(1);
390
- }
391
- }
392
-
393
- // Run CLI
394
- main().catch((error) => {
395
- console.error('Unexpected error:', error);
396
- process.exit(1);
397
- });
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Agent CLI - Interactive code analysis with Gemini AI
5
+ * This demonstrates the agentic code analysis workflow
6
+ */
7
+
8
+ import { createCodeAnalysisAgent } from './agent';
9
+ import { analyzeProject } from './index';
10
+ import type { AnalysisResult } from './types';
11
+ import * as readline from 'readline';
12
+ import * as path from 'path';
13
+
14
+ interface AgentCliArgs {
15
+ root: string;
16
+ question: string;
17
+ limit: number;
18
+ apiKey?: string;
19
+ model?: string;
20
+ interactive?: boolean;
21
+ }
22
+
23
+ interface InteractiveState {
24
+ result: AnalysisResult;
25
+ skeletonContext: string;
26
+ }
27
+
28
+ function buildSkeletonContext(result: AnalysisResult, includeScores: boolean): string {
29
+ return result.files
30
+ .map((file) => {
31
+ if (includeScores) {
32
+ return `=== ${file.path} (score: ${file.score.toFixed(2)}) ===\n${file.skeleton}`;
33
+ }
34
+ return `=== ${file.path} ===\n${file.skeleton}`;
35
+ })
36
+ .join('\n\n');
37
+ }
38
+
39
+ function printEvidence(result: AnalysisResult): void {
40
+ console.log('πŸ“š Retrieval Evidence:');
41
+ for (const file of result.files.slice(0, 10)) {
42
+ console.log(`- ${file.path} (score: ${file.score.toFixed(2)})`);
43
+ }
44
+ console.log('');
45
+ }
46
+
47
+ function printInteractiveHelp(): void {
48
+ console.log('Commands:');
49
+ console.log(' /help Show this help');
50
+ console.log(' /files List currently retrieved files');
51
+ console.log(' /symbols <query> Search symbols in loaded mapping');
52
+ console.log(' /symbol <symbolId> Show full implementation for a symbol');
53
+ console.log(' /deps <filePath> Show imports and symbols for a file');
54
+ console.log(' /refresh Re-analyze project and refresh context');
55
+ console.log(' /stats Show mapping statistics');
56
+ console.log(' /exit Quit interactive mode');
57
+ console.log('');
58
+ }
59
+
60
+ /**
61
+ * Parse command line arguments
62
+ */
63
+ function parseArgs(): AgentCliArgs | null {
64
+ const args = process.argv.slice(2);
65
+
66
+ let root = process.cwd();
67
+ let question = '';
68
+ let limit = 10;
69
+ let apiKey = process.env.GEMINI_API_KEY;
70
+ let model = 'gemini-2.0-flash-exp';
71
+ let interactive = false;
72
+
73
+ for (let i = 0; i < args.length; i++) {
74
+ const arg = args[i];
75
+
76
+ if (arg === '--root' && i + 1 < args.length) {
77
+ root = args[i + 1];
78
+ i++;
79
+ } else if (arg === '--question' && i + 1 < args.length) {
80
+ question = args[i + 1];
81
+ i++;
82
+ } else if (arg === '--limit' && i + 1 < args.length) {
83
+ limit = parseInt(args[i + 1], 10);
84
+ if (isNaN(limit) || limit < 1) {
85
+ console.error(' βœ— --limit must be a positive number');
86
+ return null;
87
+ }
88
+ i++;
89
+ } else if (arg === '--api-key' && i + 1 < args.length) {
90
+ apiKey = args[i + 1];
91
+ i++;
92
+ } else if (arg === '--model' && i + 1 < args.length) {
93
+ model = args[i + 1];
94
+ i++;
95
+ } else if (arg === '--interactive' || arg === '-i') {
96
+ interactive = true;
97
+ } else if (arg === '--help' || arg === '-h') {
98
+ printHelp();
99
+ return null;
100
+ } else {
101
+ console.error(` βœ— Unknown argument: ${arg}`);
102
+ printHelp();
103
+ return null;
104
+ }
105
+ }
106
+
107
+ if (!apiKey) {
108
+ console.error('');
109
+ console.error(' βœ— No Gemini API key. Set GEMINI_API_KEY or pass --api-key <key>.');
110
+ console.error('');
111
+ return null;
112
+ }
113
+
114
+ if (!question && !interactive) {
115
+ console.error('');
116
+ console.error(' βœ— Provide --question "<text>" or use --interactive for chat mode.');
117
+ console.error('');
118
+ printHelp();
119
+ return null;
120
+ }
121
+
122
+ return { root, question, limit, apiKey, model, interactive };
123
+ }
124
+
125
+ /**
126
+ * Print help message
127
+ */
128
+ function printHelp(): void {
129
+ console.log(`
130
+ skannr-agent β€” AI-powered code analysis (Gemini)
131
+
132
+ Usage:
133
+ skannr-agent --question "<text>" [options]
134
+ skannr-agent --interactive [options]
135
+
136
+ Options:
137
+ --root <path> Project root directory (default: current directory)
138
+ --question "<text>" Question to analyze (required unless --interactive)
139
+ --limit <number> Number of top files to analyze (default: 10)
140
+ --api-key <key> Gemini API key (or set GEMINI_API_KEY env var)
141
+ --model <name> Gemini model name (default: gemini-2.0-flash-exp)
142
+ --interactive, -i Start interactive chat mode
143
+ --help, -h Show this help message
144
+
145
+ Examples:
146
+ skannr-agent --question "How does authentication work?"
147
+ skannr-agent --interactive --root ./my-project
148
+ skannr-agent --question "Find all API endpoints" --limit 15
149
+
150
+ Environment variables:
151
+ GEMINI_API_KEY Gemini API key (alternative to --api-key)
152
+
153
+ Docs: https://skannr-ten.vercel.app
154
+ `);
155
+ }
156
+
157
+ /**
158
+ * Run single question mode
159
+ */
160
+ async function runSingleQuestion(args: AgentCliArgs): Promise<void> {
161
+ console.log('πŸ” Analyzing codebase...\n');
162
+
163
+ // Step 1: Generate skeletons with mapping
164
+ const result = await analyzeProject({
165
+ root: args.root,
166
+ question: args.question,
167
+ limit: args.limit,
168
+ generateMapping: true,
169
+ });
170
+
171
+ if (result.files.length === 0) {
172
+ console.error('');
173
+ console.error(' βœ— No files found to analyze.');
174
+ console.error('');
175
+ console.error(' Possible causes:');
176
+ console.error(' Β· --root is wrong or the tree has no matching source files');
177
+ console.error(' Β· Files are excluded by config or default excludes');
178
+ console.error('');
179
+ console.error(' Try: skannr-agent --question "..." --root .');
180
+ console.error('');
181
+ return;
182
+ }
183
+
184
+ console.log(`πŸ“Š Found ${result.files.length} relevant files\n`);
185
+
186
+ // Prepare skeleton context
187
+ const skeletonContext = buildSkeletonContext(result, true);
188
+
189
+ // Step 2: Create agent and load mapping
190
+ const agent = createCodeAnalysisAgent(args.apiKey!, args.model);
191
+ const mappingPath = path.join(result.root, 'code-analyzer.mapping.json');
192
+ agent.loadMapping(mappingPath);
193
+
194
+ const stats = agent.getStats();
195
+ console.log(`πŸ“ Loaded mapping: ${stats.totalFiles} files, ${stats.totalSymbols} symbols\n`);
196
+ console.log(`πŸ€– Agent is analyzing...\n`);
197
+
198
+ // Step 3: Send to agent
199
+ const response = await agent.chat(args.question, skeletonContext);
200
+
201
+ console.log('πŸ“‹ Agent Response:\n');
202
+ console.log(response);
203
+ console.log('\n');
204
+ printEvidence(result);
205
+ }
206
+
207
+ /**
208
+ * Run interactive mode
209
+ */
210
+ async function runInteractive(args: AgentCliArgs): Promise<void> {
211
+ console.log('πŸš€ Starting interactive code analysis agent...\n');
212
+ console.log('First, let me analyze your codebase.\n');
213
+
214
+ const buildState = async (): Promise<InteractiveState> => {
215
+ const result = await analyzeProject({
216
+ root: args.root,
217
+ question: 'code structure and organization',
218
+ limit: args.limit,
219
+ generateMapping: true,
220
+ });
221
+
222
+ if (result.files.length === 0) {
223
+ return {
224
+ result,
225
+ skeletonContext: '',
226
+ };
227
+ }
228
+
229
+ return {
230
+ result,
231
+ skeletonContext: buildSkeletonContext(result, false),
232
+ };
233
+ };
234
+
235
+ // Step 1: Generate skeletons with mapping
236
+ let state = await buildState();
237
+
238
+ if (state.result.files.length === 0) {
239
+ console.log('No files found for analysis.');
240
+ return;
241
+ }
242
+
243
+ console.log(`πŸ“Š Analyzed ${state.result.files.length} files\n`);
244
+
245
+ // Step 2: Create agent and load mapping
246
+ const agent = createCodeAnalysisAgent(args.apiKey!, args.model);
247
+ const mappingPath = path.join(state.result.root, 'code-analyzer.mapping.json');
248
+ agent.loadMapping(mappingPath);
249
+
250
+ const stats = agent.getStats();
251
+ console.log(`πŸ“ Mapping: ${stats.totalFiles} files, ${stats.totalSymbols} symbols\n`);
252
+ console.log('πŸ’¬ Ready for questions! Type /help for commands.\n');
253
+
254
+ // Step 3: Interactive loop
255
+ const rl = readline.createInterface({
256
+ input: process.stdin,
257
+ output: process.stdout,
258
+ });
259
+
260
+ const askQuestion = () => {
261
+ rl.question('You: ', async (input) => {
262
+ const question = input.trim();
263
+
264
+ if (question.toLowerCase() === 'exit' || question.toLowerCase() === '/exit') {
265
+ console.log('\nπŸ‘‹ Goodbye!');
266
+ rl.close();
267
+ return;
268
+ }
269
+
270
+ if (question.toLowerCase() === 'stats' || question.toLowerCase() === '/stats') {
271
+ console.log('\nπŸ“Š Statistics:');
272
+ console.log(JSON.stringify(agent.getStats(), null, 2));
273
+ console.log('');
274
+ askQuestion();
275
+ return;
276
+ }
277
+
278
+ if (question.toLowerCase() === '/help') {
279
+ console.log('');
280
+ printInteractiveHelp();
281
+ askQuestion();
282
+ return;
283
+ }
284
+
285
+ if (question.toLowerCase() === '/files') {
286
+ console.log('');
287
+ printEvidence(state.result);
288
+ askQuestion();
289
+ return;
290
+ }
291
+
292
+ if (question.toLowerCase() === '/refresh') {
293
+ console.log('\nπŸ”„ Refreshing project analysis...');
294
+ state = await buildState();
295
+ const refreshedMappingPath = path.join(state.result.root, 'code-analyzer.mapping.json');
296
+ agent.loadMapping(refreshedMappingPath);
297
+ console.log(`βœ… Refreshed ${state.result.files.length} files\n`);
298
+ askQuestion();
299
+ return;
300
+ }
301
+
302
+ if (question.startsWith('/symbols ')) {
303
+ const query = question.slice('/symbols '.length).trim();
304
+ if (!query) {
305
+ console.log('\nUsage: /symbols <query>\n');
306
+ askQuestion();
307
+ return;
308
+ }
309
+ const symbols = agent.searchSymbols(query);
310
+ console.log('\nπŸ”Ž Symbol Search Result:');
311
+ console.log(JSON.stringify(symbols, null, 2));
312
+ console.log('');
313
+ askQuestion();
314
+ return;
315
+ }
316
+
317
+ if (question.startsWith('/symbol ')) {
318
+ const symbolId = question.slice('/symbol '.length).trim();
319
+ if (!symbolId) {
320
+ console.log('\nUsage: /symbol <symbolId>\n');
321
+ askQuestion();
322
+ return;
323
+ }
324
+ const symbol = agent.getSymbolDetails(symbolId);
325
+ console.log('\n🧩 Symbol Details:');
326
+ console.log(JSON.stringify(symbol, null, 2));
327
+ console.log('');
328
+ askQuestion();
329
+ return;
330
+ }
331
+
332
+ if (question.startsWith('/deps ')) {
333
+ const filePath = question.slice('/deps '.length).trim();
334
+ if (!filePath) {
335
+ console.log('\nUsage: /deps <filePath>\n');
336
+ askQuestion();
337
+ return;
338
+ }
339
+ const deps = agent.analyzeFileDependencies(filePath);
340
+ console.log('\nπŸ•ΈοΈ File Dependencies:');
341
+ console.log(JSON.stringify(deps, null, 2));
342
+ console.log('');
343
+ askQuestion();
344
+ return;
345
+ }
346
+
347
+ if (!question) {
348
+ askQuestion();
349
+ return;
350
+ }
351
+
352
+ console.log('\nπŸ€– Agent: ');
353
+
354
+ try {
355
+ const response = await agent.chat(question, state.skeletonContext);
356
+ console.log(response);
357
+ console.log('');
358
+ printEvidence(state.result);
359
+ } catch (error) {
360
+ console.error('Error:', error instanceof Error ? error.message : error);
361
+ }
362
+
363
+ console.log('');
364
+ askQuestion();
365
+ });
366
+ };
367
+
368
+ askQuestion();
369
+ }
370
+
371
+ /**
372
+ * Main CLI function
373
+ */
374
+ async function main(): Promise<void> {
375
+ const args = parseArgs();
376
+
377
+ if (!args) {
378
+ process.exit(1);
379
+ }
380
+
381
+ try {
382
+ if (args.interactive) {
383
+ await runInteractive(args);
384
+ } else {
385
+ await runSingleQuestion(args);
386
+ }
387
+ } catch (error) {
388
+ console.error('Error:', error instanceof Error ? error.message : error);
389
+ process.exit(1);
390
+ }
391
+ }
392
+
393
+ // Run CLI
394
+ main().catch((error) => {
395
+ console.error('Unexpected error:', error);
396
+ process.exit(1);
397
+ });