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,111 @@
1
+ import { exec } from 'child_process';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs/promises';
4
+ import { promisify } from 'util';
5
+ const execAsync = promisify(exec);
6
+ export async function detectTestFramework(projectPath) {
7
+ const result = {
8
+ type: 'unknown',
9
+ framework: null,
10
+ cmd: null,
11
+ coverageCmd: null
12
+ };
13
+ try {
14
+ const pkgPath = path.join(projectPath, 'package.json');
15
+ const pkgData = await fs.readFile(pkgPath, 'utf-8');
16
+ const pkg = JSON.parse(pkgData);
17
+ result.type = 'node';
18
+ const scripts = pkg.scripts || {};
19
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
20
+ if (scripts.test) {
21
+ result.framework = 'npm test';
22
+ result.cmd = ['npm', 'test'];
23
+ if (deps.vitest) {
24
+ result.framework = 'vitest';
25
+ result.coverageCmd = ['npx', 'vitest', 'run', '--coverage'];
26
+ }
27
+ else if (deps.jest) {
28
+ result.framework = 'jest';
29
+ result.coverageCmd = ['npx', 'jest', '--coverage'];
30
+ }
31
+ }
32
+ else if (deps.vitest) {
33
+ result.framework = 'vitest';
34
+ result.cmd = ['npx', 'vitest', 'run'];
35
+ result.coverageCmd = ['npx', 'vitest', 'run', '--coverage'];
36
+ }
37
+ else if (deps.jest) {
38
+ result.framework = 'jest';
39
+ result.cmd = ['npx', 'jest'];
40
+ result.coverageCmd = ['npx', 'jest', '--coverage'];
41
+ }
42
+ }
43
+ catch { }
44
+ // Check python
45
+ try {
46
+ const pyproject = await fs.stat(path.join(projectPath, 'pyproject.toml')).catch(() => null);
47
+ const reqs = await fs.stat(path.join(projectPath, 'requirements.txt')).catch(() => null);
48
+ if (pyproject || reqs) {
49
+ result.type = 'python';
50
+ result.framework = 'pytest';
51
+ result.cmd = ['python', '-m', 'pytest', '-v'];
52
+ result.coverageCmd = ['python', '-m', 'pytest', '--cov', '--cov-report=term-missing'];
53
+ }
54
+ }
55
+ catch { }
56
+ return result;
57
+ }
58
+ export async function runTestRunner(projectPath = ".", withCoverage = false) {
59
+ const root = path.resolve(projectPath);
60
+ let report = `============================================================\n`;
61
+ report += `[TEST RUNNER] Unified Test Execution\n`;
62
+ report += `============================================================\n`;
63
+ const info = await detectTestFramework(root);
64
+ report += `Type: ${info.type}\nFramework: ${info.framework}\n------------------------------------------------------------\n`;
65
+ if (!info.cmd) {
66
+ report += `No test framework found for this project.\n`;
67
+ return { passed: true, report };
68
+ }
69
+ const cmdArr = (withCoverage && info.coverageCmd) ? info.coverageCmd : info.cmd;
70
+ const cmdStr = cmdArr.join(' ');
71
+ report += `Running: ${cmdStr}\n------------------------------------------------------------\n`;
72
+ let passed = false;
73
+ let testsRun = 0, testsPassed = 0, testsFailed = 0;
74
+ let output = '';
75
+ try {
76
+ // Child process maxbuffer 5MB since test output can be huge
77
+ const result = await execAsync(cmdStr, { cwd: root, maxBuffer: 5 * 1024 * 1024 });
78
+ passed = true;
79
+ output = result.stdout;
80
+ }
81
+ catch (e) {
82
+ passed = false;
83
+ output = e.stdout || e.stderr || e.message;
84
+ }
85
+ // Parse stats
86
+ const passedMatch = output.match(/(\d+)\s+passed/i);
87
+ if (passedMatch)
88
+ testsPassed = parseInt(passedMatch[1]);
89
+ const failedMatch = output.match(/(\d+)\s+failed/i);
90
+ if (failedMatch)
91
+ testsFailed = parseInt(failedMatch[1]);
92
+ testsRun = testsPassed + testsFailed;
93
+ const lines = output.split('\n');
94
+ for (const line of lines.slice(0, 30)) {
95
+ report += `${line}\n`;
96
+ }
97
+ if (lines.length > 30) {
98
+ report += `... (${lines.length - 30} more lines)\n`;
99
+ }
100
+ report += `\n============================================================\nSUMMARY\n============================================================\n`;
101
+ if (passed) {
102
+ report += `[PASS] All tests passed\n`;
103
+ }
104
+ else {
105
+ report += `[FAIL] Some tests failed\n`;
106
+ }
107
+ if (testsRun > 0) {
108
+ report += `Tests: ${testsRun} total, ${testsPassed} passed, ${testsFailed} failed\n`;
109
+ }
110
+ return { passed, report };
111
+ }
@@ -0,0 +1,150 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ async function getFiles(dir, extensionRegex, excludeList) {
4
+ let files = [];
5
+ try {
6
+ const items = await fs.readdir(dir, { withFileTypes: true });
7
+ for (const item of items) {
8
+ const fullPath = path.join(dir, item.name);
9
+ if (excludeList.some(ex => fullPath.includes(ex)))
10
+ continue;
11
+ if (item.isDirectory()) {
12
+ files = files.concat(await getFiles(fullPath, extensionRegex, excludeList));
13
+ }
14
+ else if (extensionRegex.test(item.name)) {
15
+ files.push(fullPath);
16
+ }
17
+ }
18
+ }
19
+ catch { } // Ignore read errors
20
+ return files;
21
+ }
22
+ export async function checkTypescriptCoverage(projectPath) {
23
+ const passed = [];
24
+ const issues = [];
25
+ const stats = { any_count: 0, untyped_functions: 0, total_functions: 0 };
26
+ const exclude = ['node_modules', '.d.ts', 'dist', 'build', '.next'];
27
+ const tsFiles = await getFiles(projectPath, /\.(ts|tsx)$/, exclude);
28
+ if (tsFiles.length === 0) {
29
+ return { type: 'typescript', files: 0, passed, issues: ["[!] No TypeScript files found"], stats };
30
+ }
31
+ // Check first 30 files for performance
32
+ for (const filePath of tsFiles.slice(0, 30)) {
33
+ try {
34
+ const content = await fs.readFile(filePath, 'utf-8');
35
+ // any count
36
+ const anyMatches = content.match(/:\s*any\b/g);
37
+ if (anyMatches)
38
+ stats.any_count += anyMatches.length;
39
+ // untyped functions
40
+ const untypedFuncs = content.match(/function\s+\w+\s*\([^)]*\)\s*{/g);
41
+ if (untypedFuncs)
42
+ stats.untyped_functions += untypedFuncs.length;
43
+ const untypedArrows = content.match(/=\s*\([^:)]*\)\s*=>/g);
44
+ if (untypedArrows)
45
+ stats.untyped_functions += untypedArrows.length;
46
+ // typed functions
47
+ let typedCount = 0;
48
+ const typedFuncs = content.match(/function\s+\w+\s*\([^)]*\)\s*:\s*\w+/g);
49
+ if (typedFuncs)
50
+ typedCount += typedFuncs.length;
51
+ const typedArrows = content.match(/:\s*\([^)]*\)\s*=>\s*\w+/g);
52
+ if (typedArrows)
53
+ typedCount += typedArrows.length;
54
+ stats.total_functions += (typedCount + (untypedFuncs?.length || 0) + (untypedArrows?.length || 0));
55
+ }
56
+ catch { }
57
+ }
58
+ if (stats.any_count === 0)
59
+ passed.push("[OK] No 'any' types found");
60
+ else if (stats.any_count <= 5)
61
+ issues.push(`[!] ${stats.any_count} 'any' types found (acceptable)`);
62
+ else
63
+ issues.push(`[X] ${stats.any_count} 'any' types found (too many)`);
64
+ if (stats.total_functions > 0) {
65
+ const typedRatio = ((stats.total_functions - stats.untyped_functions) / stats.total_functions) * 100;
66
+ if (typedRatio >= 80)
67
+ passed.push(`[OK] Type coverage: ${typedRatio.toFixed(0)}%`);
68
+ else if (typedRatio >= 50)
69
+ issues.push(`[!] Type coverage: ${typedRatio.toFixed(0)}% (improve)`);
70
+ else
71
+ issues.push(`[X] Type coverage: ${typedRatio.toFixed(0)}% (too low)`);
72
+ }
73
+ passed.push(`[OK] Analyzed ${tsFiles.length} TypeScript files`);
74
+ return { type: 'typescript', files: tsFiles.length, passed, issues, stats };
75
+ }
76
+ export async function checkPythonCoverage(projectPath) {
77
+ const passed = [];
78
+ const issues = [];
79
+ const stats = { any_count: 0, untyped_functions: 0, total_functions: 0, typed_functions: 0 };
80
+ const exclude = ['venv', '__pycache__', '.git', 'node_modules'];
81
+ const pyFiles = await getFiles(projectPath, /\.py$/, exclude);
82
+ if (pyFiles.length === 0) {
83
+ return { type: 'python', files: 0, passed, issues: ["[!] No Python files found"], stats };
84
+ }
85
+ for (const filePath of pyFiles.slice(0, 30)) {
86
+ try {
87
+ const content = await fs.readFile(filePath, 'utf-8');
88
+ const anyMatches = content.match(/:\s*Any\b/g);
89
+ if (anyMatches)
90
+ stats.any_count += anyMatches.length;
91
+ const allFuncs = content.match(/def\s+\w+\s*\([^)]*\)(?:\s*->[^:]+)?\s*:/g);
92
+ if (allFuncs) {
93
+ let localTyped = 0;
94
+ for (const f of allFuncs) {
95
+ const argsPart = f.substring(f.indexOf('('), f.lastIndexOf(')'));
96
+ const hasReturn = f.includes('->');
97
+ const hasTypedArgs = argsPart.includes(':');
98
+ if (hasTypedArgs || hasReturn) {
99
+ localTyped++;
100
+ }
101
+ }
102
+ stats.typed_functions += localTyped;
103
+ stats.untyped_functions += allFuncs.length - localTyped;
104
+ }
105
+ }
106
+ catch { }
107
+ }
108
+ stats.total_functions = stats.typed_functions + stats.untyped_functions;
109
+ if (stats.total_functions > 0) {
110
+ const typedRatio = (stats.typed_functions / stats.total_functions) * 100;
111
+ if (typedRatio >= 70)
112
+ passed.push(`[OK] Type hints coverage: ${typedRatio.toFixed(0)}%`);
113
+ else if (typedRatio >= 40)
114
+ issues.push(`[!] Type hints coverage: ${typedRatio.toFixed(0)}%`);
115
+ else
116
+ issues.push(`[X] Type hints coverage: ${typedRatio.toFixed(0)}% (add type hints)`);
117
+ }
118
+ if (stats.any_count === 0)
119
+ passed.push("[OK] No 'Any' types found");
120
+ else if (stats.any_count <= 3)
121
+ issues.push(`[!] ${stats.any_count} 'Any' types found`);
122
+ else
123
+ issues.push(`[X] ${stats.any_count} 'Any' types found`);
124
+ passed.push(`[OK] Analyzed ${pyFiles.length} Python files`);
125
+ return { type: 'python', files: pyFiles.length, passed, issues, stats };
126
+ }
127
+ export async function runTypeCoverage(projectPath = ".") {
128
+ const tsRes = await checkTypescriptCoverage(projectPath);
129
+ const pyRes = await checkPythonCoverage(projectPath);
130
+ const results = [tsRes, pyRes].filter(r => r.files > 0);
131
+ if (results.length === 0) {
132
+ return { passed: true, report: "[!] No TypeScript or Python files found." };
133
+ }
134
+ let report = "";
135
+ let criticalIssues = 0;
136
+ for (const res of results) {
137
+ report += `\n[${res.type.toUpperCase()}]\n----------------------------------------\n`;
138
+ for (const p of res.passed)
139
+ report += ` ${p}\n`;
140
+ for (const i of res.issues) {
141
+ report += ` ${i}\n`;
142
+ if (i.startsWith("[X]"))
143
+ criticalIssues++;
144
+ }
145
+ }
146
+ report += `\n============================================================\n`;
147
+ const passed = criticalIssues === 0;
148
+ report += passed ? `[OK] TYPE COVERAGE: ACCEPTABLE` : `[X] TYPE COVERAGE: ${criticalIssues} critical issues`;
149
+ return { passed, report };
150
+ }
@@ -0,0 +1,222 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ class UXAuditor {
4
+ issues = [];
5
+ warnings = [];
6
+ passed_count = 0;
7
+ files_checked = 0;
8
+ async auditFile(filePath) {
9
+ try {
10
+ const content = await fs.readFile(filePath, 'utf-8');
11
+ this.files_checked++;
12
+ const filename = path.basename(filePath);
13
+ const lowerC = content.toLowerCase();
14
+ const hasLongText = /<p|<div.*class=.*text|article|<span.*text/i.test(content);
15
+ const hasForm = /<form|<input|password|credit|card|payment/i.test(content);
16
+ const complexElements = (content.match(/<input|<select|<textarea|<option/gi) || []).length;
17
+ const navItems = (content.match(/<NavLink|<Link|<a\s+href|nav-item/gi) || []).length;
18
+ if (navItems > 7)
19
+ this.issues.push(`[Hick's Law] ${filename}: ${navItems} nav items (Max 7)`);
20
+ if (/height:\s*([0-3]\d)px/.test(content) || /h-[1-9]\b|h-10\b/.test(content))
21
+ this.warnings.push(`[Fitts' Law] ${filename}: Small targets (< 44px)`);
22
+ if (complexElements > 7 && !/step|wizard|stage/i.test(content))
23
+ this.warnings.push(`[Miller's Law] ${filename}: Complex form (${complexElements} fields)`);
24
+ if (lowerC.includes('button') && !/primary|bg-primary|Button.*primary|variant=["']primary/i.test(content))
25
+ this.warnings.push(`[Von Restorff] ${filename}: No primary CTA`);
26
+ if (navItems > 3) {
27
+ const navContent = [...content.matchAll(/<NavLink|<Link|<a\s+href[^>]*>([^<]+)<\/a>/gi)];
28
+ if (navContent.length > 2) {
29
+ const last = navContent[navContent.length - 1][1].toLowerCase();
30
+ if (!['contact', 'login', 'sign', 'get started', 'cta', 'button'].some(x => last.includes(x))) {
31
+ this.warnings.push(`[Serial Position] ${filename}: Last nav item may not be important.`);
32
+ }
33
+ }
34
+ }
35
+ const hasHero = /hero|<h1|banner/i.test(content);
36
+ if (hasHero) {
37
+ const hasVis = /gradient|linear-gradient|radial-gradient|@keyframes|transition:|animate-/.test(content);
38
+ if (!hasVis && !/background:|bg-/.test(content)) {
39
+ this.warnings.push(`[Visceral] ${filename}: Hero section lacks visual appeal.`);
40
+ }
41
+ }
42
+ if (/onClick|@click|onclick/i.test(content)) {
43
+ if (!/transition|animate|hover:|focus:|disabled|loading|spinner|setState|useState/i.test(content)) {
44
+ this.warnings.push(`[Behavioral] ${filename}: Interactive elements lack immediate feedback.`);
45
+ }
46
+ }
47
+ if (hasLongText && !/about|story|mission|values|why we|our journey|testimonials/i.test(content)) {
48
+ this.warnings.push(`[Reflective] ${filename}: Long-form content without brand story/values.`);
49
+ }
50
+ if (hasForm) {
51
+ if (!/ssl|secure|encrypt|lock|padlock|https/i.test(content) && !/checkout|payment/i.test(content)) {
52
+ this.warnings.push(`[Trust] ${filename}: Form without security indicators.`);
53
+ }
54
+ if (!/<label|placeholder|aria-label/i.test(content)) {
55
+ this.issues.push(`[Cognitive Load] ${filename}: Form inputs without labels.`);
56
+ }
57
+ const radioInputs = (content.match(/type=["']radio/gi) || []).length;
58
+ if (radioInputs > 0 && !/checked|selected|default|value=["'].*["']/i.test(content)) {
59
+ this.warnings.push(`[Persuasion] ${filename}: Radio buttons without default selection.`);
60
+ }
61
+ }
62
+ const socialProof = content.match(/review|testimonial|rating|star|trust|trusted by|customer|logo/gi) || [];
63
+ if (socialProof.length > 0)
64
+ this.passed_count++;
65
+ else if (hasLongText)
66
+ this.warnings.push(`[Trust] ${filename}: No social proof detected.`);
67
+ if (/footer|<footer/i.test(content) && !/certif|award|media|press|featured|as seen in/i.test(content)) {
68
+ this.warnings.push(`[Trust] ${filename}: Footer lacks authority signals.`);
69
+ }
70
+ if (complexElements > 5 && !/step|wizard|stage|accordion|collapsible|tab|more\.\.\.|advanced|show more/i.test(content)) {
71
+ this.warnings.push(`[Cognitive Load] ${filename}: Many form elements without progressive disclosure.`);
72
+ }
73
+ const manyColors = (content.match(/#[0-9a-fA-F]{3,6}|rgb|hsl/g) || []).length > 15;
74
+ const manyBorders = (content.match(/border:|border-/g) || []).length > 10;
75
+ if (manyColors && manyBorders)
76
+ this.warnings.push(`[Cognitive Load] ${filename}: High visual noise detected.`);
77
+ if (/price|pricing|cost|\$\d+/i.test(content) && !/original|was|strike|del|save \d+%/i.test(content)) {
78
+ this.warnings.push(`[Persuasion] ${filename}: Prices without anchoring.`);
79
+ }
80
+ if (/join|subscriber|member|user/i.test(content) && !/\d+[+kmb]|\d+,\d+/.test(content)) {
81
+ this.warnings.push(`[Persuasion] ${filename}: Social proof without specific numbers.`);
82
+ }
83
+ if (hasForm && complexElements > 5 && !/progress|step \d+|complete|%|bar/i.test(content)) {
84
+ this.warnings.push(`[Persuasion] ${filename}: Long form without progress indicator.`);
85
+ }
86
+ // Typography
87
+ const googleFonts = [...content.matchAll(/fonts\.googleapis\.com[^"']*family=([^"&]+)/gi)];
88
+ if (googleFonts.length > 3)
89
+ this.issues.push(`[Typography] ${filename}: >3 font families detected.`);
90
+ if (hasLongText && !/max-w-(?:prose|[\[\\]?\d+ch[\]\\]?)|max-width:\s*\d+ch/.test(content)) {
91
+ this.warnings.push(`[Typography] ${filename}: No line length constraint (45-75ch).`);
92
+ }
93
+ if (/<h[1-6]|text-(?:xl|2xl|3xl|4xl|5xl|6xl)/i.test(content)) {
94
+ const lhMatches = [...content.matchAll(/(?:leading-|line-height:\s*)([\d.]+)/g)];
95
+ for (const m of lhMatches) {
96
+ if (parseFloat(m[1]) > 1.5)
97
+ this.warnings.push(`[Typography] ${filename}: Heading has line-height > 1.3.`);
98
+ }
99
+ }
100
+ if (/uppercase|text-transform:\s*uppercase/i.test(content) && !/tracking-|letter-spacing:/.test(content)) {
101
+ this.warnings.push(`[Typography] ${filename}: Uppercase text without tracking.`);
102
+ }
103
+ if (/text-(?:4xl|5xl|6xl|7xl|8xl|9xl)|font-size:\s*[3-9]\dpx/.test(content) && !/tracking-tight|letter-spacing:\s*-[0-9]/.test(content)) {
104
+ this.warnings.push(`[Typography] ${filename}: Large display text without tracking-tight.`);
105
+ }
106
+ if (/font-size:|text-(?:xs|sm|base|lg|xl|2xl)/.test(content) && !/clamp\(|responsive:/.test(content)) {
107
+ this.warnings.push(`[Typography] ${filename}: Fixed font sizes without clamp().`);
108
+ }
109
+ const headings = [...content.matchAll(/<h([1-6])/gi)];
110
+ if (headings.length > 0) {
111
+ for (let i = 0; i < headings.length - 1; i++) {
112
+ if (parseInt(headings[i + 1][1]) > parseInt(headings[i][1]) + 1) {
113
+ this.warnings.push(`[Typography] ${filename}: Skipped heading level.`);
114
+ }
115
+ }
116
+ if (!headings.some(h => h[1] === '1') && hasLongText) {
117
+ this.warnings.push(`[Typography] ${filename}: No h1 found.`);
118
+ }
119
+ }
120
+ // Visual Effects
121
+ if (/backdrop-filter|blur\(/.test(content) && !/background:\s*rgba|bg-opacity|bg-[a-z0-9]+\/\d+/.test(content)) {
122
+ this.warnings.push(`[Visual] ${filename}: Blur used without semi-transparent background`);
123
+ }
124
+ if (/@keyframes|transition:/.test(content) && /width|height|top|left|right|bottom|margin|padding/.test(content)) {
125
+ this.warnings.push(`[Performance] ${filename}: Animating expensive properties. Use transform/opacity.`);
126
+ if (!/prefers-reduced-motion/.test(content))
127
+ this.warnings.push(`[Accessibility] ${filename}: Animations found without prefers-reduced-motion check`);
128
+ }
129
+ const neoShadows = [...content.matchAll(/box-shadow:\s*([^;]+)/g)];
130
+ for (const s of neoShadows) {
131
+ if (s[1].includes(',') && s[1].includes('-') && s[1].includes('inset')) {
132
+ this.warnings.push(`[Visual] ${filename}: Neomorphism inset detected. Ensure adequate contrast.`);
133
+ }
134
+ }
135
+ const gradients = (content.match(/gradient/gi) || []).length;
136
+ if (gradients > 5)
137
+ this.warnings.push(`[Visual] ${filename}: Many gradients detected (${gradients}).`);
138
+ else if (hasHero && !/background:|bg-/.test(content))
139
+ this.warnings.push(`[Visual] ${filename}: Hero section without visual interest.`);
140
+ if ((content.match(/border:/g) || []).length > 8)
141
+ this.warnings.push(`[Visual] ${filename}: Many border declarations`);
142
+ // colors
143
+ const purples = ['#8B5CF6', '#A855F7', '#9333EA', '#7C3AED', '#6D28D9', 'purple', 'violet', 'fuchsia', 'magenta', 'lavender'];
144
+ for (const p of purples) {
145
+ if (lowerC.includes(p.toLowerCase())) {
146
+ this.issues.push(`[Color] ${filename}: PURPLE DETECTED ('${p}'). Banned by Maestro rules.`);
147
+ break;
148
+ }
149
+ }
150
+ if (/color:\s*#000000|#000\b/.test(content))
151
+ this.warnings.push(`[Color] ${filename}: Pure black (#000000) detected.`);
152
+ // animation & UX
153
+ const durations = [...content.matchAll(/(?:duration|animation-duration|transition-duration):\s*([\d.]+)(s|ms)/g)];
154
+ for (const d of durations) {
155
+ const ms = parseFloat(d[1]) * (d[2] === 's' ? 1000 : 1);
156
+ if (ms < 50)
157
+ this.warnings.push(`[Animation] ${filename}: Very fast animation (${d[1]}${d[2]}).`);
158
+ else if (ms > 1000 && lowerC.includes('transition'))
159
+ this.warnings.push(`[Animation] ${filename}: Long transition.`);
160
+ }
161
+ if (/ease-in\s+.*entry|fade-in.*ease-in/.test(content))
162
+ this.warnings.push(`[Animation] ${filename}: Entry animation with ease-in.`);
163
+ if (/ease-out\s+.*exit|fade-out.*ease-out/.test(content))
164
+ this.warnings.push(`[Animation] ${filename}: Exit animation with ease-out.`);
165
+ const interactiveCount = (content.match(/<button|<a\s+href|onClick|@click/g) || []).length;
166
+ if (interactiveCount > 2 && !/hover:|focus:|:hover|:focus/.test(content)) {
167
+ this.warnings.push(`[Animation] ${filename}: Interactive elements without hover/focus states.`);
168
+ }
169
+ // accessibility basics
170
+ if (/<img(?![^>]*alt=)[^>]*>/.test(content))
171
+ this.issues.push(`[Accessibility] ${filename}: Missing img alt text`);
172
+ }
173
+ catch { }
174
+ }
175
+ async auditDirectory(dir) {
176
+ try {
177
+ const items = await fs.readdir(dir, { withFileTypes: true });
178
+ for (const item of items) {
179
+ if (['node_modules', '.git', 'dist', 'build', '.next'].includes(item.name))
180
+ continue;
181
+ const fullPath = path.join(dir, item.name);
182
+ if (item.isDirectory()) {
183
+ await this.auditDirectory(fullPath);
184
+ }
185
+ else if (/\.(tsx|jsx|html|vue|svelte|css)$/.test(item.name)) {
186
+ await this.auditFile(fullPath);
187
+ }
188
+ }
189
+ }
190
+ catch { }
191
+ }
192
+ getReport() {
193
+ return {
194
+ files_checked: this.files_checked,
195
+ issues: this.issues,
196
+ warnings: this.warnings,
197
+ passed_checks: this.passed_count,
198
+ compliant: this.issues.length === 0
199
+ };
200
+ }
201
+ }
202
+ export async function runUxAudit(projectPath = ".") {
203
+ const root = path.resolve(projectPath);
204
+ const auditor = new UXAuditor();
205
+ await auditor.auditDirectory(root);
206
+ const res = auditor.getReport();
207
+ let report = `\n[UX AUDIT] ${res.files_checked} files checked\n`;
208
+ report += `--------------------------------------------------\n`;
209
+ if (res.issues.length > 0) {
210
+ report += `[!] ISSUES (${res.issues.length}):\n`;
211
+ for (const i of res.issues.slice(0, 10))
212
+ report += ` - ${i}\n`;
213
+ }
214
+ if (res.warnings.length > 0) {
215
+ report += `[*] WARNINGS (${res.warnings.length}):\n`;
216
+ for (const w of res.warnings.slice(0, 15))
217
+ report += ` - ${w}\n`;
218
+ }
219
+ report += `[+] PASSED CHECKS: ${res.passed_checks}\n`;
220
+ report += `STATUS: ${res.compliant ? 'PASS' : 'FAIL'}\n`;
221
+ return { passed: res.compliant, report };
222
+ }
@@ -0,0 +1,159 @@
1
+ import * as path from 'path';
2
+ import { spawn } from 'child_process';
3
+ import { existsSync } from 'fs';
4
+ import * as fs from 'fs/promises';
5
+ import { runSecurityScan } from './validators/securityScan.js';
6
+ import { runLintRunner } from './validators/lintRunner.js';
7
+ import { runTypeCoverage } from './validators/typeCoverage.js';
8
+ import { runTestRunner } from './validators/testRunner.js';
9
+ import { runPlaywrightTest } from './validators/playwrightRunner.js';
10
+ async function hasScript(rootPath, scriptName) {
11
+ const pkgPath = path.join(rootPath, 'package.json');
12
+ if (!existsSync(pkgPath))
13
+ return false;
14
+ try {
15
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
16
+ return !!(pkg.scripts && pkg.scripts[scriptName]);
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ async function runCommand(name, command, args, projectPath) {
23
+ const start = Date.now();
24
+ return new Promise((resolve) => {
25
+ const child = spawn(command, args, { cwd: projectPath, shell: true });
26
+ let out = '';
27
+ child.stdout.on('data', data => out += data.toString());
28
+ child.stderr.on('data', data => out += data.toString());
29
+ child.on('close', code => {
30
+ const passed = code === 0;
31
+ const duration = (Date.now() - start) / 1000;
32
+ const report = passed ? `✅ ${name} (${duration}s): PASSED\n${out}` : `❌ ${name} (${duration}s): FAILED\n${out}`;
33
+ resolve({ passed, report, duration });
34
+ });
35
+ setTimeout(() => {
36
+ child.kill();
37
+ resolve({ passed: false, report: `❌ ${name}: TIMED OUT`, duration: (Date.now() - start) / 1000 });
38
+ }, 10 * 60 * 1000);
39
+ });
40
+ }
41
+ export async function runVerifyAll(projectPath, url, skipE2E = false, stopOnFail = false) {
42
+ const root = path.resolve(projectPath);
43
+ let report = `🚀 FULL VERIFICATION SUITE\nProject: ${root}\nURL: ${url}\n\n`;
44
+ let passedCount = 0;
45
+ let failedCount = 0;
46
+ let skippedCount = 0;
47
+ const VERIFICATION_SUITE = [
48
+ {
49
+ category: "Security",
50
+ checks: [
51
+ {
52
+ name: "Security Scan", required: true, fn: async () => {
53
+ const start = Date.now();
54
+ const res = await runSecurityScan(root);
55
+ return { passed: res.passed, report: res.report, duration: (Date.now() - start) / 1000 };
56
+ }
57
+ }
58
+ ]
59
+ },
60
+ {
61
+ category: "Code Quality",
62
+ checks: [
63
+ {
64
+ name: "Lint Check", required: true, fn: async () => {
65
+ const start = Date.now();
66
+ const res = await runLintRunner(root);
67
+ return { passed: res.passed, report: res.report, duration: (Date.now() - start) / 1000 };
68
+ }
69
+ },
70
+ {
71
+ name: "Type Coverage", required: false, fn: async () => {
72
+ const start = Date.now();
73
+ const res = await runTypeCoverage(root);
74
+ return { passed: res.passed, report: res.report, duration: (Date.now() - start) / 1000 };
75
+ }
76
+ }
77
+ ]
78
+ },
79
+ {
80
+ category: "Testing",
81
+ checks: [
82
+ {
83
+ name: "Test Suite", required: false, fn: async () => {
84
+ const start = Date.now();
85
+ const res = await runTestRunner(root, false);
86
+ return { passed: res.passed, report: res.report, duration: (Date.now() - start) / 1000 };
87
+ }
88
+ }
89
+ ]
90
+ },
91
+ {
92
+ category: "Build",
93
+ checks: [
94
+ {
95
+ name: "Build App", required: false, fn: async () => {
96
+ if (await hasScript(root, 'build'))
97
+ return runCommand("Build App", 'npm', ['run', 'build'], root);
98
+ return { passed: true, skipped: true, report: `Script 'build' not found in package.json` };
99
+ }
100
+ }
101
+ ]
102
+ },
103
+ {
104
+ category: "E2E Testing",
105
+ requiresUrl: true,
106
+ checks: [
107
+ {
108
+ name: "Playwright E2E", required: false, fn: async () => {
109
+ const start = Date.now();
110
+ const res = await runPlaywrightTest(url);
111
+ return {
112
+ passed: res.status === 'success',
113
+ report: JSON.stringify(res, null, 2),
114
+ duration: (Date.now() - start) / 1000
115
+ };
116
+ }
117
+ }
118
+ ]
119
+ }
120
+ ];
121
+ for (const suite of VERIFICATION_SUITE) {
122
+ if (suite.requiresUrl && !url)
123
+ continue;
124
+ if (skipE2E && suite.category === "E2E Testing")
125
+ continue;
126
+ report += `📋 ${suite.category.toUpperCase()}\n`;
127
+ for (const check of suite.checks) {
128
+ try {
129
+ const result = await check.fn();
130
+ if (result.skipped) {
131
+ skippedCount++;
132
+ report += `⏭️ ${check.name}: Skipped - ${result.report}\n`;
133
+ continue;
134
+ }
135
+ report += `[${result.duration?.toFixed(2)}s] ${check.name}:\n${result.report}\n`;
136
+ if (result.passed) {
137
+ passedCount++;
138
+ }
139
+ else {
140
+ failedCount++;
141
+ if (stopOnFail && check.required) {
142
+ report += `CRITICAL FAILURE on ${check.name}\n`;
143
+ return report + `\nTotal Passed: ${passedCount} | Failed: ${failedCount} | Skipped: ${skippedCount}\n\nFAILED❗`;
144
+ }
145
+ }
146
+ }
147
+ catch (e) {
148
+ failedCount++;
149
+ report += `❌ Error executing check ${check.name}: ${e.message}\n`;
150
+ if (stopOnFail && check.required) {
151
+ return report + `\nTotal Passed: ${passedCount} | Failed: ${failedCount} | Skipped: ${skippedCount}\n\nFAILED❗`;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ report += `\n📊 FINAL REPORT\nPassed: ${passedCount}\nFailed: ${failedCount}\nSkipped: ${skippedCount}\n\n`;
157
+ report += failedCount > 0 ? `❌ VERIFICATION FAILED!` : `✅ ALL CHECKS PASSED - Ready for deployment! ✨`;
158
+ return report;
159
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superkit-mcp-server",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "An MCP server for exploring and loading Super-Kit AI agent resources.",
6
6
  "main": "build/index.js",
@@ -14,11 +14,13 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@modelcontextprotocol/sdk": "^1.4.1",
17
+ "playwright": "^1.58.2",
17
18
  "zod": "^3.23.8"
18
19
  },
19
20
  "devDependencies": {
20
- "@types/node": "^22.10.1",
21
- "typescript": "^5.7.2"
21
+ "@types/node": "^22.19.13",
22
+ "typescript": "^5.7.2",
23
+ "vitest": "^4.0.18"
22
24
  },
23
25
  "files": [
24
26
  "build/",
@@ -77,5 +77,5 @@ Before designing an API:
77
77
 
78
78
  | Script | Purpose | Command |
79
79
  |--------|---------|---------|
80
- | `scripts/api_validator.py` | API endpoint validation | `python scripts/api_validator.py <project_path>` |
80
+ | `api_validator` validator | API endpoint validation | `call_tool_checklist` |
81
81