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
package/src/cli.ts ADDED
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI entry point for skannr
5
+ */
6
+
7
+ import { analyzeProject, getCacheManager } from './index';
8
+ import * as path from 'path';
9
+ import { loadConfig } from './config';
10
+ import { discoverModules } from './scanner';
11
+
12
+ interface CliArgs {
13
+ root: string;
14
+ question: string;
15
+ limit: number;
16
+ limitProvided: boolean;
17
+ generateMapping: boolean;
18
+ mappingOutputPath?: string;
19
+ moduleKeys?: string[];
20
+ lang: 'typescript' | 'javascript' | 'python' | 'auto';
21
+ skipCache?: boolean;
22
+ cacheCommand?: 'clear' | 'stats';
23
+ }
24
+
25
+ /**
26
+ * Parse command line arguments
27
+ */
28
+ function parseArgs(): CliArgs | null {
29
+ const args = process.argv.slice(2);
30
+
31
+ let root = process.cwd();
32
+ let question = '';
33
+ let limit = 10;
34
+ let limitProvided = false;
35
+ let generateMapping = false;
36
+ let mappingOutputPath: string | undefined;
37
+ let moduleKeys: string[] | undefined;
38
+ let lang: 'typescript' | 'javascript' | 'python' | 'auto' = 'auto';
39
+ let skipCache = false;
40
+ let cacheCommand: 'clear' | 'stats' | undefined;
41
+
42
+ for (let i = 0; i < args.length; i++) {
43
+ const arg = args[i];
44
+
45
+ if (arg === '--root' && i + 1 < args.length) {
46
+ root = args[i + 1];
47
+ i++;
48
+ } else if (arg === '--question' && i + 1 < args.length) {
49
+ question = args[i + 1];
50
+ i++;
51
+ } else if (arg === '--limit' && i + 1 < args.length) {
52
+ limit = parseInt(args[i + 1], 10);
53
+ if (isNaN(limit) || limit < 1) {
54
+ console.error('Error: --limit must be a positive number');
55
+ return null;
56
+ }
57
+ limitProvided = true;
58
+ i++;
59
+ } else if (arg === '--with-mapping') {
60
+ generateMapping = true;
61
+ } else if (arg === '--mapping-output' && i + 1 < args.length) {
62
+ mappingOutputPath = args[i + 1];
63
+ generateMapping = true; // Implies mapping generation
64
+ i++;
65
+ } else if (arg === '--modules' && i + 1 < args.length) {
66
+ moduleKeys = args[i + 1].split(',').map(k => k.trim());
67
+ i++;
68
+ } else if (arg === '--lang' && i + 1 < args.length) {
69
+ const langArg = args[i + 1].toLowerCase();
70
+ if (langArg !== 'typescript' && langArg !== 'javascript' && langArg !== 'python' && langArg !== 'auto') {
71
+ console.error('Error: --lang must be one of: typescript, javascript, python, auto');
72
+ return null;
73
+ }
74
+ lang = langArg as CliArgs['lang'];
75
+ i++;
76
+ } else if (arg === '--skip-cache') {
77
+ skipCache = true;
78
+ } else if (arg === '--cache-clear') {
79
+ cacheCommand = 'clear';
80
+ } else if (arg === '--cache-stats') {
81
+ cacheCommand = 'stats';
82
+ } else if (arg === '--help' || arg === '-h') {
83
+ printHelp();
84
+ return null;
85
+ } else {
86
+ console.error(`Unknown argument: ${arg}`);
87
+ printHelp();
88
+ return null;
89
+ }
90
+ }
91
+
92
+ // Handle cache commands
93
+ if (cacheCommand) {
94
+ return { root, question: '', limit, limitProvided, generateMapping, moduleKeys, lang, cacheCommand };
95
+ }
96
+
97
+ if (!question) {
98
+ console.error('Error: --question is required (or use --cache-* for cache management)\n');
99
+ printHelp();
100
+ return null;
101
+ }
102
+
103
+ return { root, question, limit, limitProvided, generateMapping, mappingOutputPath, moduleKeys, lang, skipCache };
104
+ }
105
+
106
+ /**
107
+ * Print help message
108
+ */
109
+ function printHelp(): void {
110
+ console.log(`
111
+ uca - Universal Code Analyzer
112
+
113
+ Usage:
114
+ uca --question "<text>" [options]
115
+ uca --cache-clear # Clear all caches
116
+ uca --cache-stats # Show cache statistics
117
+
118
+ Options:
119
+ --root <path> Project root directory (default: current directory)
120
+ --question "<text>" Natural language question about the codebase (required)
121
+ --limit <number> Number of top files to analyze (default: 10)
122
+ --with-mapping Generate symbol mapping for on-demand retrieval
123
+ --modules <keys> Comma-separated module keys (auto-discovered when omitted)
124
+ --lang <mode> typescript | javascript | python | auto (default: auto)
125
+ --mapping-output <path> Custom path for mapping file (implies --with-mapping)
126
+ --skip-cache Skip cache and force full analysis
127
+ --cache-clear Clear all cached analysis results
128
+ --cache-stats Show cache statistics (hits, misses, size)
129
+ --help, -h Show this help message
130
+
131
+ Example:
132
+ uca --question "authentication logic" --limit 5
133
+ uca --root ./my-project --question "database models"
134
+ uca --question "API endpoints" --with-mapping
135
+ uca --question "permissions" --modules auth,middleware --skip-cache
136
+ uca --question "class structure" --lang python
137
+ uca --cache-stats
138
+ uca --cache-clear
139
+
140
+ Output:
141
+ JSON object with ranked files and their code skeletons
142
+ `);
143
+ }
144
+
145
+ /**
146
+ * Main CLI function
147
+ */
148
+ async function main(): Promise<void> {
149
+ const args = parseArgs();
150
+
151
+ if (!args) {
152
+ process.exit(1);
153
+ }
154
+
155
+ try {
156
+ // Handle cache commands
157
+ if (args.cacheCommand === 'clear') {
158
+ const cacheManager = getCacheManager();
159
+ cacheManager.clear();
160
+ console.log('✅ Cache cleared successfully');
161
+ process.exit(0);
162
+ }
163
+
164
+ if (args.cacheCommand === 'stats') {
165
+ const cacheManager = getCacheManager();
166
+ const stats = cacheManager.getStats();
167
+ console.log('\n📊 Cache Statistics:');
168
+ console.log(` Hits: ${stats.hits}`);
169
+ console.log(` Misses: ${stats.misses}`);
170
+ console.log(` Hit Rate: ${stats.hitRate}%`);
171
+ console.log(` Cache Size: ${(stats.cacheSize / 1024).toFixed(2)} KB`);
172
+ console.log(` Cache Dir: ${cacheManager.getCacheDir()}\n`);
173
+ process.exit(0);
174
+ }
175
+
176
+ const absoluteRoot = path.resolve(args.root);
177
+ const config = loadConfig(absoluteRoot);
178
+ const moduleKeys =
179
+ args.moduleKeys
180
+ ?? (config.modules ? Object.keys(config.modules) : discoverModules(absoluteRoot));
181
+
182
+ const resolvedLimit = args.limitProvided ? args.limit : (config.defaultLimit ?? args.limit);
183
+ const langExtensions = resolveExtensionsForLanguage(args.lang);
184
+ const extensions = langExtensions ?? config.extensions;
185
+
186
+ const result = await analyzeProject({
187
+ root: absoluteRoot,
188
+ question: args.question,
189
+ limit: resolvedLimit,
190
+ generateMapping: args.generateMapping,
191
+ mappingOutputPath: args.mappingOutputPath,
192
+ moduleKeys,
193
+ moduleDefinitions: config.modules,
194
+ lang: args.lang,
195
+ skipCache: args.skipCache,
196
+ exclude: config.exclude,
197
+ extensions,
198
+ });
199
+ console.log(JSON.stringify(result, null, 2));
200
+ } catch (error) {
201
+ console.error('Error analyzing project:', error instanceof Error ? error.message : error);
202
+ process.exit(1);
203
+ }
204
+ }
205
+
206
+ // Run CLI
207
+ main().catch((error) => {
208
+ console.error('Unexpected error:', error);
209
+ process.exit(1);
210
+ });
211
+
212
+ function resolveExtensionsForLanguage(lang: CliArgs['lang']): string[] | undefined {
213
+ switch (lang) {
214
+ case 'typescript':
215
+ case 'javascript':
216
+ return ['.ts', '.tsx', '.js', '.jsx'];
217
+ case 'python':
218
+ return ['.py', '.pyi'];
219
+ case 'auto':
220
+ default:
221
+ return undefined;
222
+ }
223
+ }
package/src/config.ts ADDED
@@ -0,0 +1,22 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ export interface AnalyzerConfig {
5
+ modules?: Record<string, string[]>;
6
+ exclude?: string[];
7
+ extensions?: string[];
8
+ defaultLimit?: number;
9
+ }
10
+
11
+ export function loadConfig(root: string): AnalyzerConfig {
12
+ const configPath = path.join(root, 'code-analyzer.config.json');
13
+ if (fs.existsSync(configPath)) {
14
+ try {
15
+ const raw = fs.readFileSync(configPath, 'utf-8');
16
+ return JSON.parse(raw) as AnalyzerConfig;
17
+ } catch {
18
+ console.warn('[config] Failed to parse code-analyzer.config.json, using defaults');
19
+ }
20
+ }
21
+ return {};
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Main library exports for skannr
3
+ */
4
+
5
+ import * as path from 'path';
6
+ import { discoverModules, readFileContent, scanFiles, scanRocketChatFiles, scanTypeScriptFiles } from './scanner';
7
+ import { rankFiles } from './ranker';
8
+ import { rankFilesEnhanced } from './ranker-enhanced';
9
+ import { buildSkeletonForFile } from './skeletonizer';
10
+ import { countTokens } from './tokenizer';
11
+ import {
12
+ buildSkeletonWithMapping,
13
+ saveMappingToFile,
14
+ type SymbolMapping,
15
+ } from './mapper';
16
+ import {
17
+ AnalyzeOptions,
18
+ AnalysisResult,
19
+ FileAnalysis,
20
+ } from './types';
21
+ import { getCacheManager, type CacheConfig } from './cache';
22
+ import { detectRepoLanguages, getAdapter } from './languages/registry';
23
+
24
+ export async function analyzeProject(
25
+ options?: AnalyzeOptions & {
26
+ skipCache?: boolean;
27
+ cacheConfig?: Partial<CacheConfig>;
28
+ }
29
+ ): Promise<AnalysisResult> {
30
+ if (!options) {
31
+ throw new Error('Options are required (must include root and question)');
32
+ }
33
+
34
+ const {
35
+ root,
36
+ question,
37
+ limit = 10,
38
+ generateMapping = false,
39
+ mappingOutputPath,
40
+ enhancedRanking = false,
41
+ moduleKeys,
42
+ moduleDefinitions,
43
+ exclude,
44
+ extensions,
45
+ lang = 'auto',
46
+ skipCache = false,
47
+ cacheConfig,
48
+ } = options;
49
+
50
+ const absoluteRoot = path.resolve(root);
51
+ const effectiveModuleKeys =
52
+ moduleKeys && moduleKeys.length > 0 ? moduleKeys : discoverModules(absoluteRoot);
53
+ const effectiveExtensions = resolveExtensions(absoluteRoot, lang, extensions);
54
+
55
+ const allFiles = scanFiles(absoluteRoot, {
56
+ extensions: effectiveExtensions,
57
+ exclude,
58
+ moduleKeys: effectiveModuleKeys,
59
+ moduleDefinitions,
60
+ });
61
+
62
+ if (allFiles.length === 0) {
63
+ return {
64
+ question,
65
+ root: absoluteRoot,
66
+ limit,
67
+ files: [],
68
+ };
69
+ }
70
+
71
+ const cacheManager = cacheConfig ? getCacheManager() : getCacheManager();
72
+ if (cacheConfig) {
73
+ // cache manager behavior intentionally unchanged
74
+ }
75
+
76
+ if (!skipCache) {
77
+ const cachedResult = cacheManager.get(
78
+ absoluteRoot,
79
+ question,
80
+ allFiles,
81
+ effectiveModuleKeys,
82
+ enhancedRanking,
83
+ limit
84
+ );
85
+ if (cachedResult) {
86
+ console.log('📦 Cache hit! Using cached analysis result');
87
+ return cachedResult;
88
+ }
89
+ }
90
+
91
+ const rankedFiles = enhancedRanking
92
+ ? rankFilesEnhanced(allFiles, question, limit, absoluteRoot)
93
+ : rankFiles(allFiles, question, limit);
94
+
95
+ const fileAnalyses: FileAnalysis[] = [];
96
+ const symbolMapping: SymbolMapping = {
97
+ generatedAt: new Date().toISOString(),
98
+ rootPath: absoluteRoot,
99
+ files: {},
100
+ };
101
+
102
+ for (const rankedFile of rankedFiles) {
103
+ const originalContent = readFileContent(rankedFile.path);
104
+ if (!originalContent) {
105
+ continue;
106
+ }
107
+
108
+ const relativePath = path.relative(absoluteRoot, rankedFile.path);
109
+ const adapter = getAdapter(rankedFile.path);
110
+
111
+ let skeleton: string;
112
+ if (generateMapping && adapter.name === 'typescript') {
113
+ const mappingResult = buildSkeletonWithMapping(rankedFile.path, absoluteRoot);
114
+ skeleton = mappingResult.skeleton;
115
+ if (mappingResult.symbols.length > 0) {
116
+ symbolMapping.files[relativePath] = {
117
+ originalPath: rankedFile.path,
118
+ symbols: mappingResult.symbols,
119
+ };
120
+ }
121
+ } else {
122
+ skeleton = buildSkeletonForFile(rankedFile.path);
123
+ }
124
+
125
+ fileAnalyses.push({
126
+ path: relativePath,
127
+ score: rankedFile.score,
128
+ skeleton,
129
+ originalTokenCount: countTokens(originalContent),
130
+ skeletonTokenCount: countTokens(skeleton),
131
+ });
132
+ }
133
+
134
+ const result: AnalysisResult = {
135
+ question,
136
+ root: absoluteRoot,
137
+ limit,
138
+ files: fileAnalyses,
139
+ };
140
+
141
+ if (!skipCache) {
142
+ cacheManager.set(
143
+ absoluteRoot,
144
+ question,
145
+ allFiles,
146
+ result,
147
+ effectiveModuleKeys,
148
+ enhancedRanking,
149
+ limit
150
+ );
151
+ }
152
+
153
+ if (generateMapping && Object.keys(symbolMapping.files).length > 0) {
154
+ const mappingPath = mappingOutputPath || path.join(absoluteRoot, 'code-analyzer.mapping.json');
155
+ saveMappingToFile(symbolMapping, mappingPath);
156
+ console.log(`Symbol mapping saved to: ${mappingPath}`);
157
+ }
158
+
159
+ return result;
160
+ }
161
+
162
+ function resolveExtensions(
163
+ root: string,
164
+ lang: AnalyzeOptions['lang'],
165
+ explicit?: string[]
166
+ ): string[] | undefined {
167
+ if (explicit && explicit.length > 0) {
168
+ return explicit.map((ext) => ext.toLowerCase());
169
+ }
170
+
171
+ if (lang === 'typescript' || lang === 'javascript') {
172
+ return ['.ts', '.tsx', '.js', '.jsx'];
173
+ }
174
+
175
+ if (lang === 'python') {
176
+ return ['.py', '.pyi'];
177
+ }
178
+
179
+ if (lang === 'auto') {
180
+ detectRepoLanguages(root);
181
+ return undefined;
182
+ }
183
+
184
+ return undefined;
185
+ }
186
+
187
+ export { rankFiles } from './ranker';
188
+ export { rankFilesEnhanced } from './ranker-enhanced';
189
+ export { buildSkeletonForFile } from './skeletonizer';
190
+ export { scanTypeScriptFiles, scanRocketChatFiles, scanFiles, discoverModules } from './scanner';
191
+ export { countTokens } from './tokenizer';
192
+ export {
193
+ buildSkeletonWithMapping,
194
+ getSymbolDetails,
195
+ saveMappingToFile,
196
+ loadMappingFromFile,
197
+ type SymbolMapping,
198
+ type SymbolLocation,
199
+ } from './mapper';
200
+
201
+ export type {
202
+ AnalyzeOptions,
203
+ AnalysisResult,
204
+ FileAnalysis,
205
+ RankedFile,
206
+ } from './types';
207
+
208
+ export {
209
+ CacheManager,
210
+ getCacheManager,
211
+ initializeCacheManager,
212
+ DEFAULT_CACHE_CONFIG,
213
+ type CacheConfig,
214
+ type CacheEntry,
215
+ } from './cache';
@@ -0,0 +1,44 @@
1
+ import { LanguageAdapter, Symbol } from './LanguageAdapter';
2
+
3
+ export class GenericAdapter implements LanguageAdapter {
4
+ name = 'generic';
5
+ extensions: string[] = [];
6
+
7
+ canHandle(): boolean {
8
+ return true;
9
+ }
10
+
11
+ generateSkeleton(content: string): string {
12
+ return content.split('\n').slice(0, 50).join('\n');
13
+ }
14
+
15
+ extractSymbols(content: string): Symbol[] {
16
+ const symbols: Symbol[] = [];
17
+ const lines = content.split('\n');
18
+ const seen = new Set<string>();
19
+
20
+ lines.forEach((line, idx) => {
21
+ const matches = line.match(/\b([A-Z][A-Za-z0-9]+|[a-z]+_[a-z0-9_]+)\b/g) || [];
22
+ for (const match of matches) {
23
+ if (seen.has(match)) {
24
+ continue;
25
+ }
26
+ seen.add(match);
27
+ symbols.push({
28
+ name: match,
29
+ kind: 'variable',
30
+ line: idx + 1,
31
+ });
32
+ }
33
+ });
34
+
35
+ return symbols;
36
+ }
37
+
38
+ extractImports(content: string): string[] {
39
+ const lines = content.split('\n').slice(0, 100);
40
+ return lines
41
+ .filter((line) => /^\s*(import|from|#include)\b/.test(line))
42
+ .map((line) => line.trim());
43
+ }
44
+ }
@@ -0,0 +1,14 @@
1
+ export interface Symbol {
2
+ name: string;
3
+ kind: 'function' | 'class' | 'interface' | 'type' | 'variable' | 'export';
4
+ line: number;
5
+ }
6
+
7
+ export interface LanguageAdapter {
8
+ name: string;
9
+ extensions: string[];
10
+ canHandle(filePath: string): boolean;
11
+ generateSkeleton(content: string, filePath: string): string;
12
+ extractSymbols(content: string): Symbol[];
13
+ extractImports(content: string): string[];
14
+ }
@@ -0,0 +1,74 @@
1
+ import * as path from 'path';
2
+ import { LanguageAdapter, Symbol } from './LanguageAdapter';
3
+
4
+ export class PythonAdapter implements LanguageAdapter {
5
+ name = 'python';
6
+ extensions = ['.py', '.pyi'];
7
+
8
+ canHandle(filePath: string): boolean {
9
+ return this.extensions.includes(path.extname(filePath).toLowerCase());
10
+ }
11
+
12
+ generateSkeleton(content: string): string {
13
+ const lines = content.split('\n');
14
+ const output: string[] = [];
15
+ const keepers = /^(?:\s*(?:@[\w.]+(?:\([^)]*\))?|(?:async\s+)?def\s+[\w_]+\s*\(.*\)\s*(?:->\s*[^:]+)?\s*:|class\s+[\w_]+(?:\([^)]*\))?\s*:|(?:from\s+\S+\s+import\s+.+)|(?:import\s+.+)|[A-Za-z_]\w*\s*=.+))/;
16
+
17
+ for (const line of lines) {
18
+ if (!keepers.test(line)) {
19
+ continue;
20
+ }
21
+
22
+ if (/^\s*(?:async\s+)?def\s+/.test(line)) {
23
+ const indent = line.match(/^(\s*)/)?.[1] ?? '';
24
+ output.push(line);
25
+ output.push(`${indent} ...`);
26
+ continue;
27
+ }
28
+
29
+ output.push(line);
30
+ }
31
+
32
+ return output.join('\n').trim();
33
+ }
34
+
35
+ extractSymbols(content: string): Symbol[] {
36
+ const lines = content.split('\n');
37
+ const symbols: Symbol[] = [];
38
+
39
+ lines.forEach((line, idx) => {
40
+ const classMatch = line.match(/^\s*class\s+([A-Za-z_]\w*)/);
41
+ if (classMatch) {
42
+ symbols.push({ name: classMatch[1], kind: 'class', line: idx + 1 });
43
+ }
44
+
45
+ const functionMatch = line.match(/^\s*(?:async\s+)?def\s+([A-Za-z_]\w*)/);
46
+ if (functionMatch) {
47
+ symbols.push({ name: functionMatch[1], kind: 'function', line: idx + 1 });
48
+ }
49
+
50
+ const variableMatch = line.match(/^([A-Za-z_]\w*)\s*=/);
51
+ if (variableMatch) {
52
+ symbols.push({ name: variableMatch[1], kind: 'variable', line: idx + 1 });
53
+ }
54
+ });
55
+
56
+ return symbols;
57
+ }
58
+
59
+ extractImports(content: string): string[] {
60
+ const imports: string[] = [];
61
+ const lines = content.split('\n');
62
+ for (const line of lines) {
63
+ const fromMatch = line.match(/^\s*from\s+([A-Za-z0-9_.]+)\s+import\s+/);
64
+ if (fromMatch) {
65
+ imports.push(fromMatch[1]);
66
+ }
67
+ const importMatch = line.match(/^\s*import\s+(.+)/);
68
+ if (importMatch) {
69
+ imports.push(importMatch[1].split(',')[0].trim());
70
+ }
71
+ }
72
+ return imports;
73
+ }
74
+ }