vantaverse-ai-reviewer 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 +129 -0
- package/bin/cli.js +12 -0
- package/dist/ai/agent.d.ts +54 -0
- package/dist/ai/agent.d.ts.map +1 -0
- package/dist/ai/agent.js +141 -0
- package/dist/ai/agent.js.map +1 -0
- package/dist/ai/gemini-client.d.ts +37 -0
- package/dist/ai/gemini-client.d.ts.map +1 -0
- package/dist/ai/gemini-client.js +75 -0
- package/dist/ai/gemini-client.js.map +1 -0
- package/dist/ai/prompts.d.ts +25 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +176 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/auth/token-manager.d.ts +33 -0
- package/dist/auth/token-manager.d.ts.map +1 -0
- package/dist/auth/token-manager.js +97 -0
- package/dist/auth/token-manager.js.map +1 -0
- package/dist/commands/config.d.ts +12 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +84 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/scan.d.ts +14 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +110 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/core/executor.d.ts +33 -0
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/core/executor.js +149 -0
- package/dist/core/executor.js.map +1 -0
- package/dist/core/framework-detector.d.ts +20 -0
- package/dist/core/framework-detector.d.ts.map +1 -0
- package/dist/core/framework-detector.js +155 -0
- package/dist/core/framework-detector.js.map +1 -0
- package/dist/core/scanner.d.ts +34 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +172 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/core/security.d.ts +40 -0
- package/dist/core/security.d.ts.map +1 -0
- package/dist/core/security.js +118 -0
- package/dist/core/security.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/reporters/markdown.d.ts +25 -0
- package/dist/reporters/markdown.d.ts.map +1 -0
- package/dist/reporters/markdown.js +135 -0
- package/dist/reporters/markdown.js.map +1 -0
- package/dist/utils/logger.d.ts +50 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +94 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Detector - Auto-detect project type
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
const FRAMEWORK_CONFIGS = {
|
|
7
|
+
nextjs: {
|
|
8
|
+
displayName: 'Next.js',
|
|
9
|
+
description: 'React framework with SSR/SSG',
|
|
10
|
+
scanPatterns: ['app/**/*.{ts,tsx,js,jsx}', 'pages/**/*.{ts,tsx,js,jsx}', 'components/**/*.{ts,tsx,js,jsx}'],
|
|
11
|
+
ignorePatterns: ['.next/**', 'node_modules/**']
|
|
12
|
+
},
|
|
13
|
+
react: {
|
|
14
|
+
displayName: 'React',
|
|
15
|
+
description: 'React SPA',
|
|
16
|
+
scanPatterns: ['src/**/*.{ts,tsx,js,jsx}', 'components/**/*.{ts,tsx,js,jsx}'],
|
|
17
|
+
ignorePatterns: ['build/**', 'node_modules/**']
|
|
18
|
+
},
|
|
19
|
+
vite: {
|
|
20
|
+
displayName: 'Vite',
|
|
21
|
+
description: 'Vite-powered frontend',
|
|
22
|
+
scanPatterns: ['src/**/*.{ts,tsx,js,jsx,vue,svelte}'],
|
|
23
|
+
ignorePatterns: ['dist/**', 'node_modules/**']
|
|
24
|
+
},
|
|
25
|
+
angular: {
|
|
26
|
+
displayName: 'Angular',
|
|
27
|
+
description: 'Angular framework',
|
|
28
|
+
scanPatterns: ['src/**/*.{ts,html,scss,css}'],
|
|
29
|
+
ignorePatterns: ['dist/**', 'node_modules/**', '.angular/**']
|
|
30
|
+
},
|
|
31
|
+
vue: {
|
|
32
|
+
displayName: 'Vue.js',
|
|
33
|
+
description: 'Vue.js framework',
|
|
34
|
+
scanPatterns: ['src/**/*.{vue,ts,js}'],
|
|
35
|
+
ignorePatterns: ['dist/**', 'node_modules/**']
|
|
36
|
+
},
|
|
37
|
+
svelte: {
|
|
38
|
+
displayName: 'Svelte',
|
|
39
|
+
description: 'Svelte framework',
|
|
40
|
+
scanPatterns: ['src/**/*.{svelte,ts,js}'],
|
|
41
|
+
ignorePatterns: ['build/**', '.svelte-kit/**', 'node_modules/**']
|
|
42
|
+
},
|
|
43
|
+
django: {
|
|
44
|
+
displayName: 'Django',
|
|
45
|
+
description: 'Python Django framework',
|
|
46
|
+
scanPatterns: ['**/*.py', '**/templates/**/*.html'],
|
|
47
|
+
ignorePatterns: ['venv/**', '__pycache__/**', '*.pyc', 'migrations/**']
|
|
48
|
+
},
|
|
49
|
+
flask: {
|
|
50
|
+
displayName: 'Flask',
|
|
51
|
+
description: 'Python Flask framework',
|
|
52
|
+
scanPatterns: ['**/*.py', 'templates/**/*.html'],
|
|
53
|
+
ignorePatterns: ['venv/**', '__pycache__/**', '*.pyc']
|
|
54
|
+
},
|
|
55
|
+
express: {
|
|
56
|
+
displayName: 'Express.js',
|
|
57
|
+
description: 'Node.js Express backend',
|
|
58
|
+
scanPatterns: ['**/*.{ts,js}', 'routes/**/*.{ts,js}', 'controllers/**/*.{ts,js}'],
|
|
59
|
+
ignorePatterns: ['node_modules/**', 'dist/**']
|
|
60
|
+
},
|
|
61
|
+
nestjs: {
|
|
62
|
+
displayName: 'NestJS',
|
|
63
|
+
description: 'NestJS backend framework',
|
|
64
|
+
scanPatterns: ['src/**/*.ts'],
|
|
65
|
+
ignorePatterns: ['node_modules/**', 'dist/**']
|
|
66
|
+
},
|
|
67
|
+
unknown: {
|
|
68
|
+
displayName: 'Unknown',
|
|
69
|
+
description: 'Generic project',
|
|
70
|
+
scanPatterns: ['**/*.{ts,tsx,js,jsx,py,java,go,rs}'],
|
|
71
|
+
ignorePatterns: ['node_modules/**', 'dist/**', 'build/**', 'venv/**', '__pycache__/**']
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Detect the framework used in a project
|
|
76
|
+
*/
|
|
77
|
+
export async function detectFramework(repoRoot) {
|
|
78
|
+
const exists = async (file) => {
|
|
79
|
+
try {
|
|
80
|
+
await fs.promises.access(path.join(repoRoot, file));
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
// Check for Next.js
|
|
88
|
+
if (await exists('next.config.js') || await exists('next.config.ts') || await exists('next.config.mjs')) {
|
|
89
|
+
return { name: 'nextjs', ...FRAMEWORK_CONFIGS.nextjs };
|
|
90
|
+
}
|
|
91
|
+
// Check for Angular
|
|
92
|
+
if (await exists('angular.json')) {
|
|
93
|
+
return { name: 'angular', ...FRAMEWORK_CONFIGS.angular };
|
|
94
|
+
}
|
|
95
|
+
// Check for Vue
|
|
96
|
+
if (await exists('vue.config.js') || await exists('nuxt.config.js') || await exists('nuxt.config.ts')) {
|
|
97
|
+
return { name: 'vue', ...FRAMEWORK_CONFIGS.vue };
|
|
98
|
+
}
|
|
99
|
+
// Check for Svelte
|
|
100
|
+
if (await exists('svelte.config.js') || await exists('svelte.config.ts')) {
|
|
101
|
+
return { name: 'svelte', ...FRAMEWORK_CONFIGS.svelte };
|
|
102
|
+
}
|
|
103
|
+
// Check for Vite (but not specific framework)
|
|
104
|
+
if (await exists('vite.config.js') || await exists('vite.config.ts')) {
|
|
105
|
+
return { name: 'vite', ...FRAMEWORK_CONFIGS.vite };
|
|
106
|
+
}
|
|
107
|
+
// Check for NestJS
|
|
108
|
+
if (await exists('nest-cli.json')) {
|
|
109
|
+
return { name: 'nestjs', ...FRAMEWORK_CONFIGS.nestjs };
|
|
110
|
+
}
|
|
111
|
+
// Check for Django
|
|
112
|
+
if (await exists('manage.py')) {
|
|
113
|
+
return { name: 'django', ...FRAMEWORK_CONFIGS.django };
|
|
114
|
+
}
|
|
115
|
+
// Check for Flask (app.py with Flask import)
|
|
116
|
+
if (await exists('app.py') || await exists('wsgi.py')) {
|
|
117
|
+
return { name: 'flask', ...FRAMEWORK_CONFIGS.flask };
|
|
118
|
+
}
|
|
119
|
+
// Check for Express
|
|
120
|
+
if (await exists('package.json')) {
|
|
121
|
+
try {
|
|
122
|
+
const pkg = JSON.parse(await fs.promises.readFile(path.join(repoRoot, 'package.json'), 'utf-8'));
|
|
123
|
+
if (pkg.dependencies?.express || pkg.devDependencies?.express) {
|
|
124
|
+
return { name: 'express', ...FRAMEWORK_CONFIGS.express };
|
|
125
|
+
}
|
|
126
|
+
if (pkg.dependencies?.react || pkg.devDependencies?.react) {
|
|
127
|
+
return { name: 'react', ...FRAMEWORK_CONFIGS.react };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Ignore JSON parse errors
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { name: 'unknown', ...FRAMEWORK_CONFIGS.unknown };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get framework-specific analysis hints
|
|
138
|
+
*/
|
|
139
|
+
export function getFrameworkHints(framework) {
|
|
140
|
+
const hints = {
|
|
141
|
+
nextjs: 'Focus on: App Router structure, Server Components, API routes, metadata, loading/error states',
|
|
142
|
+
react: 'Focus on: Component composition, hooks usage, state management, prop drilling, memo usage',
|
|
143
|
+
vite: 'Focus on: Build configuration, HMR setup, plugin usage, environment variables',
|
|
144
|
+
angular: 'Focus on: Module structure, dependency injection, RxJS usage, template syntax',
|
|
145
|
+
vue: 'Focus on: Composition API, reactivity, Pinia/Vuex stores, template directives',
|
|
146
|
+
svelte: 'Focus on: Reactive declarations, stores, component lifecycle, SSR handling',
|
|
147
|
+
django: 'Focus on: View logic, model design, URL patterns, template security, middleware',
|
|
148
|
+
flask: 'Focus on: Route handlers, blueprints, request handling, template rendering',
|
|
149
|
+
express: 'Focus on: Middleware chain, route organization, error handling, async patterns',
|
|
150
|
+
nestjs: 'Focus on: Module structure, decorators, dependency injection, guards, pipes',
|
|
151
|
+
unknown: 'General code quality analysis'
|
|
152
|
+
};
|
|
153
|
+
return hints[framework];
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=framework-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework-detector.js","sourceRoot":"","sources":["../../src/core/framework-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scanner - Discover and categorize files in the repository
|
|
3
|
+
*/
|
|
4
|
+
import type { FrameworkInfo } from './framework-detector.js';
|
|
5
|
+
export interface ScannedFile {
|
|
6
|
+
path: string;
|
|
7
|
+
relativePath: string;
|
|
8
|
+
size: number;
|
|
9
|
+
category: FileCategory;
|
|
10
|
+
extension: string;
|
|
11
|
+
}
|
|
12
|
+
export type FileCategory = 'component' | 'page' | 'api' | 'utility' | 'config' | 'test' | 'style' | 'type' | 'other';
|
|
13
|
+
export interface ScanResult {
|
|
14
|
+
files: ScannedFile[];
|
|
15
|
+
totalSize: number;
|
|
16
|
+
byCategory: Record<FileCategory, ScannedFile[]>;
|
|
17
|
+
skipped: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Scan repository for files matching framework patterns
|
|
21
|
+
*/
|
|
22
|
+
export declare function scanRepository(repoRoot: string, framework: FrameworkInfo, options?: {
|
|
23
|
+
maxFiles?: number;
|
|
24
|
+
maxFileSizeKb?: number;
|
|
25
|
+
}): Promise<ScanResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Read multiple files with content
|
|
28
|
+
*/
|
|
29
|
+
export declare function readFiles(files: ScannedFile[], repoRoot: string, maxSizeKb?: number): Promise<Map<string, string>>;
|
|
30
|
+
/**
|
|
31
|
+
* Get a summary of scan results
|
|
32
|
+
*/
|
|
33
|
+
export declare function getScanSummary(result: ScanResult): string;
|
|
34
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,YAAY,GAClB,WAAW,GACX,MAAM,GACN,KAAK,GACL,SAAS,GACT,QAAQ,GACR,MAAM,GACN,OAAO,GACP,MAAM,GACN,OAAO,CAAC;AAEd,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;IAChD,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAwDD;;GAEG;AACH,wBAAsB,cAAc,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,aAAa,EACxB,OAAO,GAAE;IACL,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACrB,GACP,OAAO,CAAC,UAAU,CAAC,CA+ErB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC3B,KAAK,EAAE,WAAW,EAAE,EACpB,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAY,GACxB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAc9B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAgCzD"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scanner - Discover and categorize files in the repository
|
|
3
|
+
*/
|
|
4
|
+
import { glob } from 'glob';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import { SECURITY_IGNORE_PATTERNS, validatePath, safeReadFile } from './security.js';
|
|
8
|
+
/**
|
|
9
|
+
* Categorize a file based on its path and extension
|
|
10
|
+
*/
|
|
11
|
+
function categorizeFile(relativePath, extension) {
|
|
12
|
+
const lowerPath = relativePath.toLowerCase();
|
|
13
|
+
// Test files
|
|
14
|
+
if (lowerPath.includes('.test.') || lowerPath.includes('.spec.') ||
|
|
15
|
+
lowerPath.includes('__tests__') || lowerPath.includes('__test__')) {
|
|
16
|
+
return 'test';
|
|
17
|
+
}
|
|
18
|
+
// Config files
|
|
19
|
+
if (lowerPath.includes('config') || extension === 'json' ||
|
|
20
|
+
lowerPath.endsWith('.config.ts') || lowerPath.endsWith('.config.js')) {
|
|
21
|
+
return 'config';
|
|
22
|
+
}
|
|
23
|
+
// Type definitions
|
|
24
|
+
if (extension === 'd.ts' || lowerPath.includes('/types/') || lowerPath.includes('/interfaces/')) {
|
|
25
|
+
return 'type';
|
|
26
|
+
}
|
|
27
|
+
// Style files
|
|
28
|
+
if (['css', 'scss', 'sass', 'less', 'styl'].includes(extension)) {
|
|
29
|
+
return 'style';
|
|
30
|
+
}
|
|
31
|
+
// API routes
|
|
32
|
+
if (lowerPath.includes('/api/') || lowerPath.includes('/routes/') ||
|
|
33
|
+
lowerPath.includes('/controllers/') || lowerPath.includes('/handlers/')) {
|
|
34
|
+
return 'api';
|
|
35
|
+
}
|
|
36
|
+
// Pages
|
|
37
|
+
if (lowerPath.includes('/pages/') || lowerPath.includes('/app/') &&
|
|
38
|
+
(lowerPath.includes('page.') || lowerPath.includes('layout.'))) {
|
|
39
|
+
return 'page';
|
|
40
|
+
}
|
|
41
|
+
// Components
|
|
42
|
+
if (lowerPath.includes('/components/') || lowerPath.includes('/ui/')) {
|
|
43
|
+
return 'component';
|
|
44
|
+
}
|
|
45
|
+
// Utilities
|
|
46
|
+
if (lowerPath.includes('/utils/') || lowerPath.includes('/helpers/') ||
|
|
47
|
+
lowerPath.includes('/lib/') || lowerPath.includes('/services/')) {
|
|
48
|
+
return 'utility';
|
|
49
|
+
}
|
|
50
|
+
return 'other';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Scan repository for files matching framework patterns
|
|
54
|
+
*/
|
|
55
|
+
export async function scanRepository(repoRoot, framework, options = {}) {
|
|
56
|
+
const { maxFiles = 500, maxFileSizeKb = 500 } = options;
|
|
57
|
+
const maxFileSize = maxFileSizeKb * 1024;
|
|
58
|
+
const allIgnorePatterns = [
|
|
59
|
+
...SECURITY_IGNORE_PATTERNS,
|
|
60
|
+
...framework.ignorePatterns
|
|
61
|
+
];
|
|
62
|
+
const files = [];
|
|
63
|
+
const skipped = [];
|
|
64
|
+
let totalSize = 0;
|
|
65
|
+
// Scan using framework patterns
|
|
66
|
+
for (const pattern of framework.scanPatterns) {
|
|
67
|
+
const matches = await glob(pattern, {
|
|
68
|
+
cwd: repoRoot,
|
|
69
|
+
ignore: allIgnorePatterns,
|
|
70
|
+
nodir: true,
|
|
71
|
+
absolute: false
|
|
72
|
+
});
|
|
73
|
+
for (const relativePath of matches) {
|
|
74
|
+
if (files.length >= maxFiles) {
|
|
75
|
+
skipped.push(`Limit reached (${maxFiles} files)`);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
const absolutePath = path.join(repoRoot, relativePath);
|
|
79
|
+
try {
|
|
80
|
+
// Validate path is within repo
|
|
81
|
+
validatePath(absolutePath, repoRoot);
|
|
82
|
+
const stats = await fs.promises.stat(absolutePath);
|
|
83
|
+
if (stats.size > maxFileSize) {
|
|
84
|
+
skipped.push(`${relativePath} (too large: ${(stats.size / 1024).toFixed(1)}KB)`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const extension = path.extname(relativePath).slice(1).toLowerCase();
|
|
88
|
+
const category = categorizeFile(relativePath, extension);
|
|
89
|
+
// Avoid duplicates
|
|
90
|
+
if (!files.some(f => f.relativePath === relativePath)) {
|
|
91
|
+
files.push({
|
|
92
|
+
path: absolutePath,
|
|
93
|
+
relativePath,
|
|
94
|
+
size: stats.size,
|
|
95
|
+
category,
|
|
96
|
+
extension
|
|
97
|
+
});
|
|
98
|
+
totalSize += stats.size;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
skipped.push(`${relativePath} (${error instanceof Error ? error.message : 'error'})`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Group by category
|
|
107
|
+
const byCategory = {
|
|
108
|
+
component: [],
|
|
109
|
+
page: [],
|
|
110
|
+
api: [],
|
|
111
|
+
utility: [],
|
|
112
|
+
config: [],
|
|
113
|
+
test: [],
|
|
114
|
+
style: [],
|
|
115
|
+
type: [],
|
|
116
|
+
other: []
|
|
117
|
+
};
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
byCategory[file.category].push(file);
|
|
120
|
+
}
|
|
121
|
+
return { files, totalSize, byCategory, skipped };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Read multiple files with content
|
|
125
|
+
*/
|
|
126
|
+
export async function readFiles(files, repoRoot, maxSizeKb = 100) {
|
|
127
|
+
const contents = new Map();
|
|
128
|
+
const maxSize = maxSizeKb * 1024;
|
|
129
|
+
for (const file of files) {
|
|
130
|
+
try {
|
|
131
|
+
const content = await safeReadFile(file.path, repoRoot, maxSize);
|
|
132
|
+
contents.set(file.relativePath, content);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Skip files that can't be read
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return contents;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get a summary of scan results
|
|
142
|
+
*/
|
|
143
|
+
export function getScanSummary(result) {
|
|
144
|
+
const lines = [
|
|
145
|
+
`📁 Files scanned: ${result.files.length}`,
|
|
146
|
+
`📦 Total size: ${(result.totalSize / 1024).toFixed(1)} KB`,
|
|
147
|
+
''
|
|
148
|
+
];
|
|
149
|
+
const categoryEmojis = {
|
|
150
|
+
component: '🧩',
|
|
151
|
+
page: '📄',
|
|
152
|
+
api: '🔌',
|
|
153
|
+
utility: '🛠️',
|
|
154
|
+
config: '⚙️',
|
|
155
|
+
test: '🧪',
|
|
156
|
+
style: '🎨',
|
|
157
|
+
type: '📝',
|
|
158
|
+
other: '📦'
|
|
159
|
+
};
|
|
160
|
+
for (const [category, files] of Object.entries(result.byCategory)) {
|
|
161
|
+
if (files.length > 0) {
|
|
162
|
+
const emoji = categoryEmojis[category];
|
|
163
|
+
lines.push(` ${emoji} ${category}: ${files.length}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (result.skipped.length > 0) {
|
|
167
|
+
lines.push('');
|
|
168
|
+
lines.push(`⚠️ Skipped: ${result.skipped.length} files`);
|
|
169
|
+
}
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Module - Sandbox file access to repository only
|
|
3
|
+
* Prevents path traversal attacks and ensures isolation
|
|
4
|
+
*/
|
|
5
|
+
export declare class SecurityError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Normalize and resolve a path safely
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizePath(filePath: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a path is within the allowed repository root
|
|
14
|
+
*/
|
|
15
|
+
export declare function isWithinRepo(filePath: string, repoRoot: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Validate a path and throw if it's outside the repo
|
|
18
|
+
*/
|
|
19
|
+
export declare function validatePath(filePath: string, repoRoot: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Check if path is a symlink pointing outside repo
|
|
22
|
+
*/
|
|
23
|
+
export declare function isSymlinkEscape(filePath: string, repoRoot: string): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Safe file read with security validation
|
|
26
|
+
*/
|
|
27
|
+
export declare function safeReadFile(filePath: string, repoRoot: string, maxSizeBytes?: number): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Default patterns to always ignore (security sensitive)
|
|
30
|
+
*/
|
|
31
|
+
export declare const SECURITY_IGNORE_PATTERNS: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Validate that a command is safe to execute
|
|
34
|
+
*/
|
|
35
|
+
export declare function isAllowedCommand(command: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Sanitize command arguments to prevent injection
|
|
38
|
+
*/
|
|
39
|
+
export declare function sanitizeArgs(args: string[]): string[];
|
|
40
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/core/security.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,qBAAa,aAAc,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI9B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOxE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUvE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa1F;AAED;;GAEG;AACH,wBAAsB,YAAY,CAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,YAAY,GAAE,MAAoB,GACnC,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED;;GAEG;AACH,eAAO,MAAM,wBAAwB,UAUpC,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAoBzD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKrD"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Module - Sandbox file access to repository only
|
|
3
|
+
* Prevents path traversal attacks and ensures isolation
|
|
4
|
+
*/
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
export class SecurityError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'SecurityError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Normalize and resolve a path safely
|
|
15
|
+
*/
|
|
16
|
+
export function normalizePath(filePath) {
|
|
17
|
+
return path.normalize(path.resolve(filePath));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if a path is within the allowed repository root
|
|
21
|
+
*/
|
|
22
|
+
export function isWithinRepo(filePath, repoRoot) {
|
|
23
|
+
const normalizedPath = normalizePath(filePath);
|
|
24
|
+
const normalizedRoot = normalizePath(repoRoot);
|
|
25
|
+
// Ensure the path starts with the repo root
|
|
26
|
+
return normalizedPath.startsWith(normalizedRoot + path.sep) ||
|
|
27
|
+
normalizedPath === normalizedRoot;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validate a path and throw if it's outside the repo
|
|
31
|
+
*/
|
|
32
|
+
export function validatePath(filePath, repoRoot) {
|
|
33
|
+
const normalized = normalizePath(filePath);
|
|
34
|
+
if (!isWithinRepo(normalized, repoRoot)) {
|
|
35
|
+
throw new SecurityError(`Access denied: "${filePath}" is outside the repository boundary`);
|
|
36
|
+
}
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if path is a symlink pointing outside repo
|
|
41
|
+
*/
|
|
42
|
+
export async function isSymlinkEscape(filePath, repoRoot) {
|
|
43
|
+
try {
|
|
44
|
+
const stats = await fs.promises.lstat(filePath);
|
|
45
|
+
if (stats.isSymbolicLink()) {
|
|
46
|
+
const realPath = await fs.promises.realpath(filePath);
|
|
47
|
+
return !isWithinRepo(realPath, repoRoot);
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Safe file read with security validation
|
|
57
|
+
*/
|
|
58
|
+
export async function safeReadFile(filePath, repoRoot, maxSizeBytes = 1024 * 1024 // 1MB default
|
|
59
|
+
) {
|
|
60
|
+
const validated = validatePath(filePath, repoRoot);
|
|
61
|
+
// Check for symlink escape
|
|
62
|
+
if (await isSymlinkEscape(validated, repoRoot)) {
|
|
63
|
+
throw new SecurityError(`Access denied: "${filePath}" is a symlink pointing outside the repository`);
|
|
64
|
+
}
|
|
65
|
+
// Check file size
|
|
66
|
+
const stats = await fs.promises.stat(validated);
|
|
67
|
+
if (stats.size > maxSizeBytes) {
|
|
68
|
+
throw new SecurityError(`File too large: "${filePath}" (${(stats.size / 1024 / 1024).toFixed(2)}MB > ${(maxSizeBytes / 1024 / 1024).toFixed(2)}MB limit)`);
|
|
69
|
+
}
|
|
70
|
+
return fs.promises.readFile(validated, 'utf-8');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Default patterns to always ignore (security sensitive)
|
|
74
|
+
*/
|
|
75
|
+
export const SECURITY_IGNORE_PATTERNS = [
|
|
76
|
+
'**/.git/**',
|
|
77
|
+
'**/.env',
|
|
78
|
+
'**/.env.*',
|
|
79
|
+
'**/node_modules/**',
|
|
80
|
+
'**/*.pem',
|
|
81
|
+
'**/*.key',
|
|
82
|
+
'**/secrets/**',
|
|
83
|
+
'**/.aws/**',
|
|
84
|
+
'**/.ssh/**',
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* Validate that a command is safe to execute
|
|
88
|
+
*/
|
|
89
|
+
export function isAllowedCommand(command) {
|
|
90
|
+
const allowedCommands = [
|
|
91
|
+
'git',
|
|
92
|
+
'npm',
|
|
93
|
+
'npx',
|
|
94
|
+
'node',
|
|
95
|
+
'tsc',
|
|
96
|
+
'eslint',
|
|
97
|
+
'prettier',
|
|
98
|
+
'cat',
|
|
99
|
+
'head',
|
|
100
|
+
'tail',
|
|
101
|
+
'wc',
|
|
102
|
+
'find',
|
|
103
|
+
'ls',
|
|
104
|
+
'dir',
|
|
105
|
+
];
|
|
106
|
+
const baseCommand = command.trim().split(/\s+/)[0];
|
|
107
|
+
return allowedCommands.includes(baseCommand);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Sanitize command arguments to prevent injection
|
|
111
|
+
*/
|
|
112
|
+
export function sanitizeArgs(args) {
|
|
113
|
+
return args.map(arg => {
|
|
114
|
+
// Remove shell metacharacters
|
|
115
|
+
return arg.replace(/[;&|`$(){}[\]<>]/g, '');
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/core/security.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Reviewer CLI - Main Entry Point
|
|
3
|
+
*/
|
|
4
|
+
import { program } from 'commander';
|
|
5
|
+
import { scan } from './commands/scan.js';
|
|
6
|
+
import { config } from './commands/config.js';
|
|
7
|
+
// Package info
|
|
8
|
+
const VERSION = '0.1.0';
|
|
9
|
+
const NAME = 'ai-reviewer';
|
|
10
|
+
program
|
|
11
|
+
.name(NAME)
|
|
12
|
+
.description('🤖 AI-powered code review CLI - analyze your codebase with Gemini AI')
|
|
13
|
+
.version(VERSION);
|
|
14
|
+
// Scan command (main command)
|
|
15
|
+
program
|
|
16
|
+
.command('scan')
|
|
17
|
+
.description('Analyze the current repository with AI')
|
|
18
|
+
.option('-o, --output <file>', 'Output file path', 'AI_REVIEW_REPORT.md')
|
|
19
|
+
.option('-j, --json', 'Also generate JSON output')
|
|
20
|
+
.option('-t, --types <types>', 'Analysis types (comma-separated): overview,security,codeQuality,accessibility,uiux,testing')
|
|
21
|
+
.option('-v, --verbose', 'Verbose output')
|
|
22
|
+
.action(async (opts) => {
|
|
23
|
+
await scan(opts);
|
|
24
|
+
});
|
|
25
|
+
// Config command
|
|
26
|
+
program
|
|
27
|
+
.command('config')
|
|
28
|
+
.description('Manage AI Reviewer configuration')
|
|
29
|
+
.option('-a, --action <action>', 'Action: show, reset, token', 'show')
|
|
30
|
+
.action(async (opts) => {
|
|
31
|
+
await config(opts);
|
|
32
|
+
});
|
|
33
|
+
// Default command (no args = scan)
|
|
34
|
+
program
|
|
35
|
+
.action(async () => {
|
|
36
|
+
await scan({});
|
|
37
|
+
});
|
|
38
|
+
// Parse and run
|
|
39
|
+
program.parse();
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAoB,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAsB,MAAM,sBAAsB,CAAC;AAElE,eAAe;AACf,MAAM,OAAO,GAAG,OAAO,CAAC;AACxB,MAAM,IAAI,GAAG,aAAa,CAAC;AAE3B,OAAO;KACF,IAAI,CAAC,IAAI,CAAC;KACV,WAAW,CAAC,sEAAsE,CAAC;KACnF,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,8BAA8B;AAC9B,OAAO;KACF,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,wCAAwC,CAAC;KACrD,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,EAAE,qBAAqB,CAAC;KACxE,MAAM,CAAC,YAAY,EAAE,2BAA2B,CAAC;KACjD,MAAM,CAAC,qBAAqB,EAAE,4FAA4F,CAAC;KAC3H,MAAM,CAAC,eAAe,EAAE,gBAAgB,CAAC;KACzC,MAAM,CAAC,KAAK,EAAE,IAAiB,EAAE,EAAE;IAChC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC,CAAC,CAAC;AAEP,iBAAiB;AACjB,OAAO;KACF,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,uBAAuB,EAAE,4BAA4B,EAAE,MAAM,CAAC;KACrE,MAAM,CAAC,KAAK,EAAE,IAAmB,EAAE,EAAE;IAClC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC;AAEP,mCAAmC;AACnC,OAAO;KACF,MAAM,CAAC,KAAK,IAAI,EAAE;IACf,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC;AACnB,CAAC,CAAC,CAAC;AAEP,gBAAgB;AAChB,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Reporter - Generate the final report
|
|
3
|
+
*/
|
|
4
|
+
import type { AgentResult } from '../ai/agent.js';
|
|
5
|
+
import type { FrameworkInfo } from '../core/framework-detector.js';
|
|
6
|
+
import type { ScanResult } from '../core/scanner.js';
|
|
7
|
+
export interface ReportMetadata {
|
|
8
|
+
framework: FrameworkInfo;
|
|
9
|
+
scanResult: ScanResult;
|
|
10
|
+
generatedAt: Date;
|
|
11
|
+
modelName: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generate the full markdown report
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateMarkdownReport(result: AgentResult, metadata: ReportMetadata): string;
|
|
17
|
+
/**
|
|
18
|
+
* Write report to file
|
|
19
|
+
*/
|
|
20
|
+
export declare function writeReport(content: string, outputPath: string): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Generate JSON report
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateJsonReport(result: AgentResult, metadata: ReportMetadata): object;
|
|
25
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/reporters/markdown.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,aAAa,CAAC;IACzB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,IAAI,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,cAAc,GACzB,MAAM,CAyGR;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC7B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,cAAc,GACzB,MAAM,CAmBR"}
|