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