superkit-mcp-server 1.0.1 → 1.0.2

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 (67) hide show
  1. package/ARCHITECTURE.md +2 -3
  2. package/README.md +1 -0
  3. package/build/index.js +75 -0
  4. package/build/tools/autoPreview.js +99 -0
  5. package/build/tools/checklist.js +120 -0
  6. package/build/tools/sessionManager.js +107 -0
  7. package/build/tools/validators/__tests__/apiSchema.test.js +77 -0
  8. package/build/tools/validators/__tests__/convertRules.test.js +38 -0
  9. package/build/tools/validators/__tests__/frontendDesign.test.js +55 -0
  10. package/build/tools/validators/__tests__/geoChecker.test.js +45 -0
  11. package/build/tools/validators/__tests__/i18nChecker.test.js +32 -0
  12. package/build/tools/validators/__tests__/lintRunner.test.js +65 -0
  13. package/build/tools/validators/__tests__/mobileAudit.test.js +40 -0
  14. package/build/tools/validators/__tests__/playwrightRunner.test.js +55 -0
  15. package/build/tools/validators/__tests__/reactPerformanceChecker.test.js +49 -0
  16. package/build/tools/validators/__tests__/securityScan.test.js +42 -0
  17. package/build/tools/validators/__tests__/seoChecker.test.js +44 -0
  18. package/build/tools/validators/__tests__/testRunner.test.js +49 -0
  19. package/build/tools/validators/__tests__/typeCoverage.test.js +62 -0
  20. package/build/tools/validators/accessibilityChecker.js +124 -0
  21. package/build/tools/validators/apiValidator.js +140 -0
  22. package/build/tools/validators/convertRules.js +170 -0
  23. package/build/tools/validators/geoChecker.js +176 -0
  24. package/build/tools/validators/i18nChecker.js +205 -0
  25. package/build/tools/validators/lighthouseAudit.js +50 -0
  26. package/build/tools/validators/lintRunner.js +106 -0
  27. package/build/tools/validators/mobileAudit.js +190 -0
  28. package/build/tools/validators/playwrightRunner.js +101 -0
  29. package/build/tools/validators/reactPerformanceChecker.js +199 -0
  30. package/build/tools/validators/schemaValidator.js +105 -0
  31. package/build/tools/validators/securityScan.js +215 -0
  32. package/build/tools/validators/seoChecker.js +122 -0
  33. package/build/tools/validators/testRunner.js +111 -0
  34. package/build/tools/validators/typeCoverage.js +150 -0
  35. package/build/tools/validators/uxAudit.js +222 -0
  36. package/build/tools/verifyAll.js +159 -0
  37. package/package.json +5 -3
  38. package/skills/tech/api-patterns/SKILL.md +1 -1
  39. package/skills/tech/clean-code/SKILL.md +14 -14
  40. package/skills/tech/doc.md +3 -3
  41. package/skills/tech/frontend-design/SKILL.md +1 -1
  42. package/skills/tech/geo-fundamentals/SKILL.md +1 -1
  43. package/skills/tech/i18n-localization/SKILL.md +1 -1
  44. package/skills/tech/lint-and-validate/SKILL.md +2 -2
  45. package/skills/tech/mobile-design/SKILL.md +1 -1
  46. package/skills/tech/nextjs-react-expert/SKILL.md +1 -1
  47. package/skills/tech/parallel-agents/SKILL.md +3 -3
  48. package/skills/tech/performance-profiling/SKILL.md +1 -1
  49. package/skills/tech/vulnerability-scanner/SKILL.md +1 -1
  50. package/skills/tech/webapp-testing/SKILL.md +3 -3
  51. package/workflows/review-compound.md +1 -1
  52. package/skills/tech/api-patterns/scripts/api_validator.py +0 -211
  53. package/skills/tech/database-design/scripts/schema_validator.py +0 -172
  54. package/skills/tech/frontend-design/scripts/accessibility_checker.py +0 -183
  55. package/skills/tech/frontend-design/scripts/ux_audit.py +0 -722
  56. package/skills/tech/geo-fundamentals/scripts/geo_checker.py +0 -289
  57. package/skills/tech/i18n-localization/scripts/i18n_checker.py +0 -241
  58. package/skills/tech/lint-and-validate/scripts/lint_runner.py +0 -184
  59. package/skills/tech/lint-and-validate/scripts/type_coverage.py +0 -173
  60. package/skills/tech/mobile-design/scripts/mobile_audit.py +0 -670
  61. package/skills/tech/nextjs-react-expert/scripts/convert_rules.py +0 -222
  62. package/skills/tech/nextjs-react-expert/scripts/react_performance_checker.py +0 -252
  63. package/skills/tech/performance-profiling/scripts/lighthouse_audit.py +0 -76
  64. package/skills/tech/seo-fundamentals/scripts/seo_checker.py +0 -219
  65. package/skills/tech/testing-patterns/scripts/test_runner.py +0 -219
  66. package/skills/tech/vulnerability-scanner/scripts/security_scan.py +0 -458
  67. package/skills/tech/webapp-testing/scripts/playwright_runner.py +0 -173
@@ -0,0 +1,199 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ export class PerformanceChecker {
4
+ projectPath;
5
+ issues = [];
6
+ warnings = [];
7
+ constructor(projectPath) {
8
+ this.projectPath = path.resolve(projectPath);
9
+ }
10
+ async getFiles(extensions) {
11
+ let files = [];
12
+ const self = this;
13
+ async function search(dir) {
14
+ try {
15
+ const items = await fs.readdir(dir, { withFileTypes: true });
16
+ for (const item of items) {
17
+ if (['node_modules', '.git', 'dist', 'build', '.next'].includes(item.name))
18
+ continue;
19
+ const fullPath = path.join(dir, item.name);
20
+ if (item.isDirectory())
21
+ await search(fullPath);
22
+ else if (extensions.includes(path.extname(item.name).toLowerCase())) {
23
+ files.push(fullPath);
24
+ }
25
+ }
26
+ }
27
+ catch { }
28
+ }
29
+ await search(this.projectPath);
30
+ return files;
31
+ }
32
+ async checkWaterfalls(files) {
33
+ for (const file of files) {
34
+ try {
35
+ const content = await fs.readFile(file, 'utf-8');
36
+ const lines = content.split('\n');
37
+ let consecutiveAwaits = 0;
38
+ for (let i = 0; i < lines.length; i++) {
39
+ const line = lines[i];
40
+ if (/await\s+/.test(line)) {
41
+ consecutiveAwaits++;
42
+ if (consecutiveAwaits > 1) {
43
+ this.issues.push({
44
+ file: path.relative(this.projectPath, file),
45
+ type: 'CRITICAL',
46
+ issue: 'Sequential awaits detected (waterfall)',
47
+ fix: 'Use Promise.all() for parallel fetching',
48
+ section: '1-async-eliminating-waterfalls.md'
49
+ });
50
+ break; // report once per file is enough for test
51
+ }
52
+ }
53
+ else if (line.trim().length > 0 && !line.trim().startsWith('//')) {
54
+ consecutiveAwaits = 0;
55
+ }
56
+ }
57
+ }
58
+ catch { }
59
+ }
60
+ }
61
+ async checkBarrelImports(files) {
62
+ for (const file of files) {
63
+ try {
64
+ const content = await fs.readFile(file, 'utf-8');
65
+ if (/import.*from\s+['"](@\/.*?)\/index['"]/.test(content) || /import.*from\s+['"]\.\.?\/.*?['"](?!\.tsx?)/.test(content)) {
66
+ this.warnings.push({
67
+ file: path.relative(this.projectPath, file),
68
+ type: 'CRITICAL',
69
+ issue: 'Potential barrel imports detected',
70
+ fix: 'Import directly from specific files',
71
+ section: '2-bundle-bundle-size-optimization.md'
72
+ });
73
+ }
74
+ }
75
+ catch { }
76
+ }
77
+ }
78
+ async checkDynamicImports(files) {
79
+ for (const file of files) {
80
+ try {
81
+ const content = await fs.readFile(file, 'utf-8');
82
+ if (content.length > 10000) {
83
+ const filename = path.parse(file).name;
84
+ for (const checkFile of files) {
85
+ if (checkFile === file)
86
+ continue;
87
+ const checkContent = await fs.readFile(checkFile, 'utf-8');
88
+ if ((checkContent.includes(`import ${filename}`) || checkContent.includes(`import { ${filename}`)) && !checkContent.includes('dynamic(')) {
89
+ this.warnings.push({
90
+ file: path.relative(this.projectPath, checkFile),
91
+ type: 'CRITICAL',
92
+ issue: `Large component ${filename} imported statically`,
93
+ fix: 'Use dynamic() for code splitting',
94
+ section: '2-bundle-bundle-size-optimization.md'
95
+ });
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ }
101
+ catch { }
102
+ }
103
+ }
104
+ async checkUseEffectFetching(files) {
105
+ for (const file of files) {
106
+ try {
107
+ const content = await fs.readFile(file, 'utf-8');
108
+ if (content.includes('useEffect') && /useEffect.*?fetch\(/s.test(content)) {
109
+ this.warnings.push({
110
+ file: path.relative(this.projectPath, file),
111
+ type: 'MEDIUM', // Mapped from MEDIUM-HIGH to fit interface
112
+ issue: 'Data fetching in useEffect',
113
+ fix: 'Consider using SWR or React Query for deduplication',
114
+ section: '4-client-client-side-data-fetching.md'
115
+ });
116
+ }
117
+ }
118
+ catch { }
119
+ }
120
+ }
121
+ async checkMissingMemoization(files) {
122
+ for (const file of files.filter(f => f.endsWith('.tsx'))) {
123
+ try {
124
+ const content = await fs.readFile(file, 'utf-8');
125
+ const components = content.match(/(?:export\s+)?(?:const|function)\s+([A-Z]\w+)/g);
126
+ if (components && !content.includes('React.memo') && !content.includes('memo(')) {
127
+ if (content.includes('props:') || content.includes('Props>')) {
128
+ this.warnings.push({
129
+ file: path.relative(this.projectPath, file),
130
+ type: 'MEDIUM',
131
+ issue: 'Component with props not memoized',
132
+ fix: 'Consider using React.memo if props are stable',
133
+ section: '5-rerender-re-render-optimization.md'
134
+ });
135
+ }
136
+ }
137
+ }
138
+ catch { }
139
+ }
140
+ }
141
+ async checkImageOptimization(files) {
142
+ for (const file of files) {
143
+ try {
144
+ const content = await fs.readFile(file, 'utf-8');
145
+ if (content.includes('<img') && !content.includes('next/image')) {
146
+ this.warnings.push({
147
+ file: path.relative(this.projectPath, file),
148
+ type: 'MEDIUM',
149
+ issue: 'Using <img> instead of next/image',
150
+ fix: 'Use next/image for automatic optimization',
151
+ section: '6-rendering-rendering-performance.md'
152
+ });
153
+ }
154
+ }
155
+ catch { }
156
+ }
157
+ }
158
+ async runAll() {
159
+ const tsJsFiles = await this.getFiles(['.ts', '.tsx', '.js', '.jsx']);
160
+ const tsxFiles = tsJsFiles.filter(f => f.endsWith('.tsx') || f.endsWith('.ts')); // for dynamic/effects
161
+ await this.checkWaterfalls(tsJsFiles);
162
+ await this.checkBarrelImports(tsJsFiles);
163
+ await this.checkDynamicImports(tsxFiles); // Usually larger components are in ts/tsx
164
+ await this.checkUseEffectFetching(tsxFiles);
165
+ await this.checkMissingMemoization(tsxFiles);
166
+ await this.checkImageOptimization(tsJsFiles);
167
+ return {
168
+ issues: this.issues,
169
+ warnings: this.warnings,
170
+ passed: this.issues.length === 0 && this.warnings.filter(w => w.type === 'CRITICAL').length === 0
171
+ };
172
+ }
173
+ }
174
+ export async function runReactPerformanceChecker(projectPath = ".") {
175
+ const checker = new PerformanceChecker(projectPath);
176
+ const result = await checker.runAll();
177
+ let report = `============================================================\n`;
178
+ report += `REACT PERFORMANCE AUDIT REPORT\n`;
179
+ report += `============================================================\n`;
180
+ const crits = result.issues.filter(i => i.type === 'CRITICAL');
181
+ report += `\n[CRITICAL ISSUES] (${crits.length})\n`;
182
+ for (const issue of crits) {
183
+ report += ` - ${issue.file}\n Issue: ${issue.issue}\n Fix: ${issue.fix}\n Reference: ${issue.section}\n\n`;
184
+ }
185
+ report += `\n[WARNINGS] (${result.warnings.length})\n`;
186
+ for (const warning of result.warnings.slice(0, 10)) {
187
+ report += ` - ${warning.file}\n Issue: ${warning.issue}\n Fix: ${warning.fix}\n Reference: ${warning.section}\n\n`;
188
+ }
189
+ if (result.warnings.length > 10)
190
+ report += ` ... and ${result.warnings.length - 10} more warnings\n`;
191
+ report += `\n============================================================\nSUMMARY:\n`;
192
+ report += ` Critical Issues: ${crits.length}\n Warnings: ${result.warnings.length}\n`;
193
+ report += `============================================================\n`;
194
+ if (result.issues.length === 0 && result.warnings.length === 0)
195
+ report += `\n[SUCCESS] No major performance issues detected!\n`;
196
+ else
197
+ report += `\n[ACTION REQUIRED] Review and fix issues above\nPriority: CRITICAL > HIGH > MEDIUM > LOW\n`;
198
+ return { passed: result.passed, report };
199
+ }
@@ -0,0 +1,105 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ export async function findSchemaFiles(projectPath) {
4
+ const schemas = [];
5
+ async function search(dir) {
6
+ try {
7
+ const items = await fs.readdir(dir, { withFileTypes: true });
8
+ for (const item of items) {
9
+ if (['node_modules', '.git', 'dist'].includes(item.name))
10
+ continue;
11
+ const fullPath = path.join(dir, item.name);
12
+ if (item.isDirectory()) {
13
+ await search(fullPath);
14
+ }
15
+ else {
16
+ if (item.name === 'schema.prisma') {
17
+ schemas.push(fullPath);
18
+ }
19
+ else if (fullPath.includes('drizzle') || fullPath.includes('schema') || fullPath.includes('table')) {
20
+ if (item.name.endsWith('.ts'))
21
+ schemas.push(fullPath);
22
+ }
23
+ }
24
+ }
25
+ }
26
+ catch { }
27
+ }
28
+ await search(projectPath);
29
+ return schemas.slice(0, 10);
30
+ }
31
+ export async function validatePrismaSchema(filePath) {
32
+ const issues = [];
33
+ try {
34
+ const content = await fs.readFile(filePath, 'utf-8');
35
+ // Match models roughly: model Name { body }
36
+ const modelMatches = [...content.matchAll(/model\s+(\w+)\s*{([^}]+)}/g)];
37
+ for (const match of modelMatches) {
38
+ const modelName = match[1];
39
+ const modelBody = match[2];
40
+ if (modelName[0] !== modelName[0].toUpperCase()) {
41
+ issues.push(`Model '${modelName}' should be PascalCase`);
42
+ }
43
+ if (!modelBody.includes('@id') && !modelBody.toLowerCase().includes('id')) {
44
+ issues.push(`Model '${modelName}' might be missing @id field`);
45
+ }
46
+ if (!modelBody.includes('createdAt') && !modelBody.includes('created_at')) {
47
+ issues.push(`Model '${modelName}' missing createdAt field (recommended)`);
48
+ }
49
+ // Check for index suggestions
50
+ const fks = [...modelBody.matchAll(/(\w+Id)\s+\w+/g)].map(m => m[1]);
51
+ for (const fk of fks) {
52
+ if (!content.includes(`@@index([${fk}])`) && !content.includes(`@@index(["${fk}"])`)) {
53
+ issues.push(`Consider adding @@index([${fk}]) for better query in ${modelName}`);
54
+ }
55
+ }
56
+ }
57
+ const enumMatches = [...content.matchAll(/enum\s+(\w+)\s*{/g)];
58
+ for (const match of enumMatches) {
59
+ if (match[1][0] !== match[1][0].toUpperCase()) {
60
+ issues.push(`Enum '${match[1]}' should be PascalCase`);
61
+ }
62
+ }
63
+ }
64
+ catch (e) {
65
+ issues.push(`Error reading schema: ${e.message.substring(0, 50)}`);
66
+ }
67
+ return issues;
68
+ }
69
+ export async function runSchemaValidator(projectPath = ".") {
70
+ const root = path.resolve(projectPath);
71
+ let report = `============================================================\n`;
72
+ report += `[SCHEMA VALIDATOR] Database Schema Validation\n`;
73
+ report += `============================================================\n`;
74
+ report += `Project: ${root}\n------------------------------------------------------------\n`;
75
+ const schemas = await findSchemaFiles(root);
76
+ report += `Found ${schemas.length} schema files\n`;
77
+ if (schemas.length === 0) {
78
+ report += "No schema files found\n";
79
+ return { passed: true, report };
80
+ }
81
+ const allIssues = [];
82
+ for (const file of schemas) {
83
+ const type = file.endsWith('.prisma') ? 'prisma' : 'drizzle';
84
+ report += `\nValidating: ${path.basename(file)} (${type})\n`;
85
+ const issues = type === 'prisma' ? await validatePrismaSchema(file) : [];
86
+ if (issues.length > 0) {
87
+ allIssues.push({ file: path.basename(file), type, issues });
88
+ }
89
+ }
90
+ report += `\n============================================================\nSCHEMA ISSUES\n============================================================\n`;
91
+ if (allIssues.length > 0) {
92
+ for (const item of allIssues) {
93
+ report += `\n${item.file} (${item.type}):\n`;
94
+ for (const issue of item.issues.slice(0, 5)) {
95
+ report += ` - ${issue}\n`;
96
+ }
97
+ if (item.issues.length > 5)
98
+ report += ` ... and ${item.issues.length - 5} more issues\n`;
99
+ }
100
+ }
101
+ else {
102
+ report += "No schema issues found!\n";
103
+ }
104
+ return { passed: true, report }; // Schema is usually just warnings
105
+ }
@@ -0,0 +1,215 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { existsSync } from 'fs';
5
+ // Patterns to mimic security_scan.py
6
+ const SECRET_PATTERNS = [
7
+ { regex: /api[_-]?key\s*[=:]\s*["'][^"']{10,}["']/i, type: "API Key", severity: "high" },
8
+ { regex: /token\s*[=:]\s*["'][^"']{10,}["']/i, type: "Token", severity: "high" },
9
+ { regex: /bearer\s+[a-zA-Z0-9\-_.]+/i, type: "Bearer Token", severity: "critical" },
10
+ { regex: /AKIA[0-9A-Z]{16}/, type: "AWS Access Key", severity: "critical" },
11
+ { regex: /password\s*[=:]\s*["'][^"']{4,}["']/i, type: "Password", severity: "high" },
12
+ { regex: /(mongodb|postgres|mysql|redis):\/\/[^\s"']+/, type: "Database Connection", severity: "critical" },
13
+ { regex: /-----BEGIN\s+(RSA|PRIVATE|EC)\s+KEY-----/, type: "Private Key", severity: "critical" },
14
+ ];
15
+ const DANGEROUS_PATTERNS = [
16
+ { regex: /eval\s*\(/i, name: "eval() usage", severity: "critical", category: "Code Injection risk" },
17
+ { regex: /child_process\.exec\s*\(/i, name: "child_process.exec", severity: "high", category: "Command Injection risk" },
18
+ { regex: /\.innerHTML\s*=/i, name: "innerHTML assignment", severity: "medium", category: "XSS risk" },
19
+ { regex: /dangerouslySetInnerHTML/i, name: "dangerouslySetInnerHTML", severity: "high", category: "XSS risk" },
20
+ { regex: /verify\s*=\s*False/i, name: "SSL Verify Disabled", severity: "high", category: "MITM risk" },
21
+ { regex: /disable[_-]?ssl/i, name: "SSL Disabled", severity: "high", category: "MITM risk" }
22
+ ];
23
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '__pycache__', '.venv', 'venv', '.next']);
24
+ const CODE_EXTENSIONS = new Set(['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.java', '.rb', '.php']);
25
+ const CONFIG_EXTENSIONS = new Set(['.json', '.yaml', '.yml', '.toml', '.env', '.env.local', '.env.development']);
26
+ async function getFilesByExtensions(dir, extensions) {
27
+ let files = [];
28
+ try {
29
+ const items = await fs.readdir(dir, { withFileTypes: true });
30
+ for (const item of items) {
31
+ if (SKIP_DIRS.has(item.name))
32
+ continue;
33
+ const fullPath = path.join(dir, item.name);
34
+ if (item.isDirectory()) {
35
+ files = files.concat(await getFilesByExtensions(fullPath, extensions));
36
+ }
37
+ else if (extensions.has(path.extname(item.name).toLowerCase())) {
38
+ files.push(fullPath);
39
+ }
40
+ }
41
+ }
42
+ catch { } // Ignore read errors
43
+ return files;
44
+ }
45
+ export async function scanDependencies(projectPath) {
46
+ const results = { tool: "dependency_scanner", findings: [], status: "[OK] Secure" };
47
+ const lockFiles = {
48
+ "npm": ["package-lock.json", "npm-shrinkwrap.json"],
49
+ "yarn": ["yarn.lock"],
50
+ "pnpm": ["pnpm-lock.yaml"],
51
+ "pip": ["requirements.txt", "Pipfile.lock", "poetry.lock"],
52
+ };
53
+ for (const [manager, files] of Object.entries(lockFiles)) {
54
+ const pkgFile = manager === 'pip' ? 'requirements.txt' : 'package.json';
55
+ if (existsSync(path.join(projectPath, pkgFile))) {
56
+ const hasLock = files.some(f => existsSync(path.join(projectPath, f)));
57
+ if (!hasLock) {
58
+ results.findings.push({
59
+ type: "Missing Lock File",
60
+ severity: "high",
61
+ message: `${manager}: No lock file found. Supply chain integrity at risk.`
62
+ });
63
+ }
64
+ }
65
+ }
66
+ if (existsSync(path.join(projectPath, "package.json"))) {
67
+ await new Promise((resolve) => {
68
+ const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
69
+ const child = spawn(cmd, ['audit', '--json'], { cwd: projectPath });
70
+ let out = '';
71
+ child.stdout.on('data', d => out += d.toString());
72
+ child.on('close', () => {
73
+ try {
74
+ const auditData = JSON.parse(out);
75
+ const vulns = auditData.vulnerabilities || {};
76
+ let crit = 0, high = 0;
77
+ for (const v of Object.values(vulns)) {
78
+ const sev = v.severity || 'low';
79
+ if (sev === 'critical')
80
+ crit++;
81
+ if (sev === 'high')
82
+ high++;
83
+ }
84
+ if (crit > 0) {
85
+ results.status = "[!!] Critical vulnerabilities";
86
+ results.findings.push({ type: "npm audit", severity: "critical", message: `${crit} critical vulnerabilities` });
87
+ }
88
+ else if (high > 0) {
89
+ results.status = "[!] High vulnerabilities";
90
+ results.findings.push({ type: "npm audit", severity: "high", message: `${high} high vulnerabilities` });
91
+ }
92
+ }
93
+ catch { }
94
+ resolve();
95
+ });
96
+ child.on('error', () => resolve());
97
+ });
98
+ }
99
+ if (results.findings.length === 0)
100
+ results.status = "[OK] Supply chain checks passed";
101
+ return results;
102
+ }
103
+ export async function scanSecrets(projectPath) {
104
+ const results = {
105
+ tool: "secret_scanner",
106
+ findings: [],
107
+ status: "[OK] No secrets detected",
108
+ scanned_files: 0,
109
+ by_severity: { critical: 0, high: 0, medium: 0 }
110
+ };
111
+ // Merge Sets for secret scanning
112
+ const exts = new Set([...CODE_EXTENSIONS, ...CONFIG_EXTENSIONS]);
113
+ const files = await getFilesByExtensions(projectPath, exts);
114
+ results.scanned_files = files.length;
115
+ for (const file of files) {
116
+ try {
117
+ const content = await fs.readFile(file, 'utf-8');
118
+ for (const pattern of SECRET_PATTERNS) {
119
+ const matches = content.match(new RegExp(pattern.regex, 'g'));
120
+ if (matches) {
121
+ results.findings.push({
122
+ file: path.relative(projectPath, file),
123
+ type: pattern.type,
124
+ severity: pattern.severity,
125
+ count: matches.length
126
+ });
127
+ results.by_severity[pattern.severity] += matches.length;
128
+ }
129
+ }
130
+ }
131
+ catch { }
132
+ }
133
+ if (results.by_severity.critical > 0)
134
+ results.status = "[!!] CRITICAL: Secrets exposed!";
135
+ else if (results.by_severity.high > 0)
136
+ results.status = "[!] HIGH: Secrets found";
137
+ else if (results.by_severity.high > 0 || results.by_severity.medium > 0)
138
+ results.status = "[?] Potential secrets detected";
139
+ results.findings = results.findings.slice(0, 15);
140
+ return results;
141
+ }
142
+ export async function scanCodePatterns(projectPath) {
143
+ const results = {
144
+ tool: "pattern_scanner",
145
+ findings: [],
146
+ status: "[OK] No dangerous patterns",
147
+ scanned_files: 0,
148
+ by_category: {}
149
+ };
150
+ const files = await getFilesByExtensions(projectPath, CODE_EXTENSIONS);
151
+ results.scanned_files = files.length;
152
+ for (const file of files) {
153
+ try {
154
+ const content = await fs.readFile(file, 'utf-8');
155
+ const lines = content.split('\n');
156
+ lines.forEach((line, index) => {
157
+ for (const pattern of DANGEROUS_PATTERNS) {
158
+ if (pattern.regex.test(line)) {
159
+ results.findings.push({
160
+ file: path.relative(projectPath, file),
161
+ line: index + 1,
162
+ pattern: pattern.name,
163
+ severity: pattern.severity,
164
+ category: pattern.category,
165
+ snippet: line.trim().substring(0, 80)
166
+ });
167
+ results.by_category[pattern.category] = (results.by_category[pattern.category] || 0) + 1;
168
+ }
169
+ }
170
+ });
171
+ }
172
+ catch { }
173
+ }
174
+ const critCount = results.findings.filter(f => f.severity === 'critical').length;
175
+ const highCount = results.findings.filter(f => f.severity === 'high').length;
176
+ if (critCount > 0)
177
+ results.status = `[!!] CRITICAL: ${critCount} dangerous patterns`;
178
+ else if (highCount > 0)
179
+ results.status = `[!] HIGH: ${highCount} risky patterns`;
180
+ else if (results.findings.length > 0)
181
+ results.status = "[?] Some patterns need review";
182
+ results.findings = results.findings.slice(0, 20);
183
+ return results;
184
+ }
185
+ export async function runSecurityScan(projectPath = ".", scanType = "all") {
186
+ const report = {
187
+ project: path.resolve(projectPath),
188
+ timestamp: new Date().toISOString(),
189
+ scan_type: scanType,
190
+ scans: {},
191
+ summary: { total_findings: 0, critical: 0, high: 0, overall_status: "[OK] SECURE" }
192
+ };
193
+ if (scanType === "all" || scanType === "deps")
194
+ report.scans.dependencies = await scanDependencies(projectPath);
195
+ if (scanType === "all" || scanType === "secrets")
196
+ report.scans.secrets = await scanSecrets(projectPath);
197
+ if (scanType === "all" || scanType === "patterns")
198
+ report.scans.code_patterns = await scanCodePatterns(projectPath);
199
+ for (const scan of Object.values(report.scans)) {
200
+ report.summary.total_findings += scan.findings.length;
201
+ for (const finding of scan.findings) {
202
+ if (finding.severity === 'critical')
203
+ report.summary.critical++;
204
+ if (finding.severity === 'high')
205
+ report.summary.high++;
206
+ }
207
+ }
208
+ if (report.summary.critical > 0)
209
+ report.summary.overall_status = "[!!] CRITICAL ISSUES FOUND";
210
+ else if (report.summary.high > 0)
211
+ report.summary.overall_status = "[!] HIGH RISK ISSUES";
212
+ else if (report.summary.total_findings > 0)
213
+ report.summary.overall_status = "[?] REVIEW RECOMMENDED";
214
+ return report;
215
+ }
@@ -0,0 +1,122 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ const SKIP_DIRS = ['node_modules', '.next', 'dist', 'build', '.git', '.github', '.vscode', '.idea', 'coverage', 'test', 'tests', '__tests__', 'spec', 'docs', 'documentation', 'examples'];
4
+ const SKIP_PATTERNS = ['config', 'setup', 'util', 'helper', 'hook', 'context', 'store', 'service', 'api', 'lib', 'constant', 'type', 'interface', 'mock', '.test.', '.spec.', '_test.', '_spec.'];
5
+ function isPageFile(filePath) {
6
+ const name = path.basename(filePath).toLowerCase();
7
+ const stem = path.parse(filePath).name.toLowerCase();
8
+ if (SKIP_PATTERNS.some(skip => name.includes(skip)))
9
+ return false;
10
+ const parts = filePath.toLowerCase().split(path.sep);
11
+ const pageDirs = ['pages', 'app', 'routes', 'views', 'screens'];
12
+ if (pageDirs.some(d => parts.includes(d)))
13
+ return true;
14
+ const pageNames = ['page', 'index', 'home', 'about', 'contact', 'blog', 'post', 'article', 'product', 'landing', 'layout'];
15
+ if (pageNames.some(p => stem.includes(p)))
16
+ return true;
17
+ const ext = path.extname(filePath).toLowerCase();
18
+ if (ext === '.html' || ext === '.htm')
19
+ return true;
20
+ return false;
21
+ }
22
+ export async function checkSeoPage(filePath) {
23
+ const issues = [];
24
+ try {
25
+ const content = await fs.readFile(filePath, 'utf-8');
26
+ const isLayout = content.includes('Head>') || content.toLowerCase().includes('<head');
27
+ if (isLayout && !content.toLowerCase().includes('<title') && !content.includes('title=')) {
28
+ issues.push("Missing <title> tag");
29
+ }
30
+ if (isLayout && !content.toLowerCase().includes('name="description"') && !content.toLowerCase().includes("name='description'")) {
31
+ issues.push("Missing meta description");
32
+ }
33
+ if (isLayout && !content.includes('og:') && !content.toLowerCase().includes('property="og:')) {
34
+ issues.push("Missing Open Graph tags");
35
+ }
36
+ const h1Matches = content.match(/<h1[^>]*>/gi) || [];
37
+ if (h1Matches.length > 1) {
38
+ issues.push(`Multiple H1 tags (${h1Matches.length})`);
39
+ }
40
+ const imgMatches = content.match(/<img[^>]+>/gi) || [];
41
+ for (const img of imgMatches) {
42
+ const imgLower = img.toLowerCase();
43
+ if (!imgLower.includes('alt=')) {
44
+ issues.push("Image missing alt attribute");
45
+ break;
46
+ }
47
+ if (img.includes('alt=""') || img.includes("alt=''")) {
48
+ issues.push("Image has empty alt attribute");
49
+ break;
50
+ }
51
+ }
52
+ return { file: path.basename(filePath), issues };
53
+ }
54
+ catch (e) {
55
+ return { file: path.basename(filePath), issues: [`Error: ${e.message}`] };
56
+ }
57
+ }
58
+ export async function runSeoChecker(projectPath = ".") {
59
+ const pages = [];
60
+ const root = path.resolve(projectPath);
61
+ async function search(dir) {
62
+ if (pages.length >= 50)
63
+ return;
64
+ try {
65
+ const items = await fs.readdir(dir, { withFileTypes: true });
66
+ for (const item of items) {
67
+ if (SKIP_DIRS.includes(item.name))
68
+ continue;
69
+ const fullPath = path.join(dir, item.name);
70
+ if (item.isDirectory()) {
71
+ await search(fullPath);
72
+ }
73
+ else if (/\.(html|htm|jsx|tsx)$/i.test(item.name)) {
74
+ if (isPageFile(fullPath)) {
75
+ pages.push(fullPath);
76
+ }
77
+ }
78
+ }
79
+ }
80
+ catch { }
81
+ }
82
+ await search(root);
83
+ let report = `============================================================\n`;
84
+ report += `SEO CHECKER - Search Engine Optimization Audit\n`;
85
+ report += `============================================================\n`;
86
+ if (pages.length === 0) {
87
+ report += `\n[!] No page files found.\n`;
88
+ return { passed: true, report };
89
+ }
90
+ report += `Found ${pages.length} page files to analyze\n\n`;
91
+ report += `============================================================\n`;
92
+ report += `SEO ANALYSIS RESULTS\n`;
93
+ report += `============================================================\n`;
94
+ const allIssues = [];
95
+ for (const page of pages) {
96
+ const result = await checkSeoPage(page);
97
+ if (result.issues.length > 0) {
98
+ allIssues.push(result);
99
+ }
100
+ }
101
+ if (allIssues.length > 0) {
102
+ const counts = {};
103
+ allIssues.forEach(item => {
104
+ item.issues.forEach(issue => counts[issue] = (counts[issue] || 0) + 1);
105
+ });
106
+ report += `\nIssue Summary:\n`;
107
+ Object.entries(counts).sort((a, b) => b[1] - a[1]).forEach(([issue, count]) => {
108
+ report += ` [${count}] ${issue}\n`;
109
+ });
110
+ report += `\nAffected files (${allIssues.length}):\n`;
111
+ for (const item of allIssues.slice(0, 5)) {
112
+ report += ` - ${item.file}\n`;
113
+ }
114
+ if (allIssues.length > 5)
115
+ report += ` ... and ${allIssues.length - 5} more\n`;
116
+ }
117
+ else {
118
+ report += `\n[OK] No SEO issues found!\n`;
119
+ }
120
+ const totalIssues = allIssues.reduce((sum, item) => sum + item.issues.length, 0);
121
+ return { passed: totalIssues === 0, report };
122
+ }