vibes-plug 1.0.0 → 2.5.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/.github/workflows/publish.yml +20 -0
- package/AGENTS.md +66 -0
- package/BLUEPRINT.md +152 -60
- package/CHANGELOG.md +50 -0
- package/README.md +346 -194
- package/package.json +25 -25
- package/plugin.json +1 -1
- package/skills/ai-cost-token-optimizer/SKILL.md +52 -0
- package/skills/ai-llm-integration-expert/SKILL.md +180 -162
- package/skills/api-design-expert/SKILL.md +314 -310
- package/skills/app-analyzer-optimizer/SKILL.md +193 -189
- package/skills/apple-ecosystem-expert/SKILL.md +142 -0
- package/skills/async-queue-temporal-expert/SKILL.md +54 -0
- package/skills/authentication-identity-expert/SKILL.md +251 -20
- package/skills/auto-doc-updater/SKILL.md +214 -204
- package/skills/autonomous-chaos-monkey/SKILL.md +63 -0
- package/skills/autonomous-red-teamer/SKILL.md +59 -0
- package/skills/autonomous-swarm-director/SKILL.md +69 -0
- package/skills/autonomous-tdd-debugger/SKILL.md +65 -0
- package/skills/bootstrap-to-modern/SKILL.md +90 -86
- package/skills/brainstorming/SKILL.md +373 -353
- package/skills/browser-automation-expert/SKILL.md +46 -0
- package/skills/ci-cd-devops-architect/SKILL.md +72 -45
- package/skills/cloud-hosting-expert/SKILL.md +244 -244
- package/skills/coderabbit/SKILL.md +192 -192
- package/skills/cron-scheduler-expert/SKILL.md +298 -0
- package/skills/data-telemetry-expert/SKILL.md +213 -213
- package/skills/database-orm-expert/SKILL.md +294 -294
- package/skills/dependency-upgrade-migrator/SKILL.md +295 -0
- package/skills/design-system-architect/SKILL.md +27 -10
- package/skills/doku-mcp-server/SKILL.md +251 -0
- package/skills/doku-payment-gateway/SKILL.md +227 -0
- package/skills/e2e-testing-expert/SKILL.md +315 -315
- package/skills/edge-serverless-db-expert/SKILL.md +43 -0
- package/skills/email-notification-expert/SKILL.md +362 -0
- package/skills/error-resilience-expert/SKILL.md +480 -0
- package/skills/event-driven-architect/SKILL.md +81 -81
- package/skills/feature-flag-analytics-expert/SKILL.md +46 -0
- package/skills/file-upload-media-expert/SKILL.md +431 -0
- package/skills/form-validation-expert/SKILL.md +401 -0
- package/skills/fullstack-expert/SKILL.md +202 -202
- package/skills/fullstack-expert/references/api_design_guide.md +466 -466
- package/skills/fullstack-expert/references/multi_language_backend.md +528 -528
- package/skills/fullstack-expert/scripts/api_contract_validator.py +253 -253
- package/skills/fullstack-expert/scripts/architecture_analyzer.py +326 -326
- package/skills/gemini-agent-booster/SKILL.md +135 -135
- package/skills/global-a11y-i18n-expert/SKILL.md +81 -81
- package/skills/glsl-shader-expert/SKILL.md +101 -0
- package/skills/go-programming-expert/SKILL.md +295 -295
- package/skills/graphql-apollo-expert/SKILL.md +108 -0
- package/skills/hig/SKILL.md +188 -188
- package/skills/hyper-context-synthesizer/SKILL.md +55 -0
- package/skills/js-backend-expert/SKILL.md +34 -9
- package/skills/legacy-code-translator/SKILL.md +65 -0
- package/skills/llm-cost-arbitrage-router/SKILL.md +59 -0
- package/skills/logging-error-tracking-expert/SKILL.md +338 -0
- package/skills/mcp-client-orchestrator/SKILL.md +70 -0
- package/skills/mcp-server-architect/SKILL.md +194 -194
- package/skills/micro-frontend-architect/SKILL.md +106 -0
- package/skills/mobile-expo-expert/SKILL.md +186 -186
- package/skills/mobile-push-notification-expert/SKILL.md +51 -0
- package/skills/monday-design-aesthetic/SKILL.md +67 -67
- package/skills/monorepo-architect/SKILL.md +227 -227
- package/skills/mpa-orchestrator/SKILL.md +101 -101
- package/skills/multi-agent-orchestration/SKILL.md +234 -234
- package/skills/multiple-entry-points/SKILL.md +55 -55
- package/skills/mvc-expert/SKILL.md +231 -231
- package/skills/payment-gateway-expert/SKILL.md +45 -45
- package/skills/performance-web-vitals/SKILL.md +332 -332
- package/skills/post-quantum-crypto-migrator/SKILL.md +57 -0
- package/skills/prd-architect/SKILL.md +201 -191
- package/skills/proactive-background-watcher/SKILL.md +62 -0
- package/skills/production-ready-hardener/PRODUCTION_READINESS_REPORT.md +67 -0
- package/skills/production-ready-hardener/SKILL.md +173 -186
- package/skills/production-ready-hardener/references/production_checklist.md +161 -161
- package/skills/production-ready-hardener/scripts/production_readiness_scanner.py +881 -875
- package/skills/project-context-mapper/SKILL.md +79 -0
- package/skills/python-programming-expert/SKILL.md +263 -132
- package/skills/rate-limit-abuse-prevention/SKILL.md +371 -0
- package/skills/realtime-collaboration-expert/SKILL.md +45 -45
- package/skills/rust-programming-expert/SKILL.md +235 -235
- package/skills/saas-billing/SKILL.md +377 -377
- package/skills/saas-multi-tenant/SKILL.md +251 -237
- package/skills/saas-mvp-launcher/SKILL.md +10 -0
- package/skills/saas-transformer/SKILL.md +187 -144
- package/skills/saas-transformer/references/billing_integration_guide.md +401 -401
- package/skills/saas-transformer/references/feature_gating_patterns.md +137 -137
- package/skills/saas-transformer/references/saas_transformation_checklist.md +121 -121
- package/skills/saas-transformer/scripts/saas_transformation_scanner.py +39 -29
- package/skills/scalability-clean-code/SKILL.md +229 -229
- package/skills/self-evolving-memory-graph/SKILL.md +75 -0
- package/skills/self-healing-cloud-orchestrator/SKILL.md +57 -0
- package/skills/senior-frontend/SKILL.md +161 -161
- package/skills/senior-fullstack/SKILL.md +167 -167
- package/skills/seo/SKILL.md +235 -225
- package/skills/seo-geo/SKILL.md +188 -188
- package/skills/session-context-loader/SKILL.md +77 -0
- package/skills/session-handoff-resume/SKILL.md +158 -158
- package/skills/skill_baru/SKILL.md +172 -147
- package/skills/spa-orchestrator/SKILL.md +288 -288
- package/skills/state-management-expert/SKILL.md +272 -272
- package/skills/supabase-security-expert/SKILL.md +243 -243
- package/skills/tailwind-expert/SKILL.md +188 -188
- package/skills/tanstack-query-expert/SKILL.md +199 -199
- package/skills/token-saver/SKILL.md +119 -111
- package/skills/typescript-expert/SKILL.md +324 -279
- package/skills/ui-components-expert/SKILL.md +263 -46
- package/skills/ui-ux-pro-max/SKILL.md +202 -201
- package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-310.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-310.pyc +0 -0
- package/skills/ui_ux_expert/SKILL.md +17 -6
- package/skills/vector-db-rag-expert/SKILL.md +52 -0
- package/skills/vibe-code-gardener/SKILL.md +181 -173
- package/skills/visual-qa-vision-agent/SKILL.md +65 -0
- package/skills/vue-frontend-expert/SKILL.md +126 -0
- package/skills/web-3d-graphics-expert/SKILL.md +131 -0
- package/skills/web-game-engine-expert/SKILL.md +96 -0
- package/skills/web-scraper/SKILL.md +207 -205
- package/skills/website-design-cloner/SKILL.md +174 -0
- package/skills/webxr-ar-vr-expert/SKILL.md +117 -0
- package/skills/zero-to-prod-orchestrator/SKILL.md +206 -180
- package/skills/zero-trust-secret-vault/SKILL.md +40 -0
- package/vibes-swarm-demo.gif +0 -0
|
@@ -1,326 +1,326 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Fullstack Architecture Analyzer
|
|
4
|
-
Analyzes fullstack project structure, detects anti-patterns, and validates architecture health.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import json
|
|
10
|
-
import argparse
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Dict, List, Optional
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ArchitectureAnalyzer:
|
|
16
|
-
"""Analyzes fullstack project architecture for anti-patterns and best practices."""
|
|
17
|
-
|
|
18
|
-
# Common fullstack project indicators
|
|
19
|
-
FRONTEND_MARKERS = {
|
|
20
|
-
'package.json', 'next.config.js', 'next.config.ts', 'next.config.mjs',
|
|
21
|
-
'vite.config.ts', 'vite.config.js', 'nuxt.config.ts', 'svelte.config.js',
|
|
22
|
-
'astro.config.mjs',
|
|
23
|
-
}
|
|
24
|
-
BACKEND_MARKERS = {
|
|
25
|
-
'requirements.txt', 'pyproject.toml', 'go.mod', 'Cargo.toml',
|
|
26
|
-
'pom.xml', 'build.gradle', 'Gemfile',
|
|
27
|
-
}
|
|
28
|
-
INFRA_MARKERS = {
|
|
29
|
-
'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
|
|
30
|
-
'terraform.tf', 'main.tf', 'pulumi.yaml', 'serverless.yml',
|
|
31
|
-
'k8s/', 'kubernetes/', 'helm/',
|
|
32
|
-
}
|
|
33
|
-
CONFIG_MARKERS = {
|
|
34
|
-
'.env', '.env.example', '.env.local',
|
|
35
|
-
'tsconfig.json', 'eslint.config.js', '.eslintrc.js',
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
def __init__(self, target_path: str, verbose: bool = False):
|
|
39
|
-
self.target_path = Path(target_path)
|
|
40
|
-
self.verbose = verbose
|
|
41
|
-
self.results: Dict = {
|
|
42
|
-
'status': 'pending',
|
|
43
|
-
'target': str(self.target_path),
|
|
44
|
-
'stack': {},
|
|
45
|
-
'findings': [],
|
|
46
|
-
'score': 100,
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
def run(self) -> Dict:
|
|
50
|
-
"""Execute the full architecture analysis."""
|
|
51
|
-
print(f"🏗️ Running Architecture Analyzer...")
|
|
52
|
-
print(f"📁 Target: {self.target_path}")
|
|
53
|
-
|
|
54
|
-
try:
|
|
55
|
-
self.validate_target()
|
|
56
|
-
self.detect_stack()
|
|
57
|
-
self.check_project_structure()
|
|
58
|
-
self.check_security_basics()
|
|
59
|
-
self.check_database_patterns()
|
|
60
|
-
self.check_testing_setup()
|
|
61
|
-
self.check_ci_cd()
|
|
62
|
-
self.check_docker_setup()
|
|
63
|
-
self.generate_report()
|
|
64
|
-
|
|
65
|
-
self.results['status'] = 'success'
|
|
66
|
-
print("✅ Analysis completed!")
|
|
67
|
-
return self.results
|
|
68
|
-
|
|
69
|
-
except Exception as e:
|
|
70
|
-
print(f"❌ Error: {e}")
|
|
71
|
-
sys.exit(1)
|
|
72
|
-
|
|
73
|
-
def validate_target(self):
|
|
74
|
-
"""Validate the target path exists."""
|
|
75
|
-
if not self.target_path.exists():
|
|
76
|
-
raise ValueError(f"Target path does not exist: {self.target_path}")
|
|
77
|
-
if self.verbose:
|
|
78
|
-
print(f"✓ Target validated: {self.target_path}")
|
|
79
|
-
|
|
80
|
-
def detect_stack(self):
|
|
81
|
-
"""Detect the technology stack used in the project."""
|
|
82
|
-
stack = {'frontend': [], 'backend': [], 'infrastructure': [], 'database': []}
|
|
83
|
-
|
|
84
|
-
# Check for frontend frameworks
|
|
85
|
-
if (self.target_path / 'next.config.js').exists() or \
|
|
86
|
-
(self.target_path / 'next.config.ts').exists() or \
|
|
87
|
-
(self.target_path / 'next.config.mjs').exists():
|
|
88
|
-
stack['frontend'].append('Next.js')
|
|
89
|
-
|
|
90
|
-
if (self.target_path / 'vite.config.ts').exists() or \
|
|
91
|
-
(self.target_path / 'vite.config.js').exists():
|
|
92
|
-
stack['frontend'].append('Vite')
|
|
93
|
-
|
|
94
|
-
if (self.target_path / 'nuxt.config.ts').exists():
|
|
95
|
-
stack['frontend'].append('Nuxt')
|
|
96
|
-
|
|
97
|
-
# Check for backend languages
|
|
98
|
-
if (self.target_path / 'go.mod').exists():
|
|
99
|
-
stack['backend'].append('Go')
|
|
100
|
-
if (self.target_path / 'Cargo.toml').exists():
|
|
101
|
-
stack['backend'].append('Rust')
|
|
102
|
-
if (self.target_path / 'pyproject.toml').exists() or \
|
|
103
|
-
(self.target_path / 'requirements.txt').exists():
|
|
104
|
-
stack['backend'].append('Python')
|
|
105
|
-
|
|
106
|
-
# Check package.json for more details
|
|
107
|
-
pkg_json = self.target_path / 'package.json'
|
|
108
|
-
if pkg_json.exists():
|
|
109
|
-
try:
|
|
110
|
-
with open(pkg_json) as f:
|
|
111
|
-
pkg = json.load(f)
|
|
112
|
-
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
|
113
|
-
|
|
114
|
-
if 'drizzle-orm' in deps:
|
|
115
|
-
stack['database'].append('Drizzle ORM')
|
|
116
|
-
if 'prisma' in deps or '@prisma/client' in deps:
|
|
117
|
-
stack['database'].append('Prisma')
|
|
118
|
-
if 'hono' in deps:
|
|
119
|
-
stack['backend'].append('Hono')
|
|
120
|
-
if 'fastify' in deps:
|
|
121
|
-
stack['backend'].append('Fastify')
|
|
122
|
-
if 'express' in deps:
|
|
123
|
-
stack['backend'].append('Express')
|
|
124
|
-
except (json.JSONDecodeError, IOError):
|
|
125
|
-
pass
|
|
126
|
-
|
|
127
|
-
# Check for infrastructure
|
|
128
|
-
if (self.target_path / 'Dockerfile').exists():
|
|
129
|
-
stack['infrastructure'].append('Docker')
|
|
130
|
-
if (self.target_path / 'docker-compose.yml').exists() or \
|
|
131
|
-
(self.target_path / 'docker-compose.yaml').exists():
|
|
132
|
-
stack['infrastructure'].append('Docker Compose')
|
|
133
|
-
if (self.target_path / 'main.tf').exists():
|
|
134
|
-
stack['infrastructure'].append('Terraform')
|
|
135
|
-
|
|
136
|
-
self.results['stack'] = stack
|
|
137
|
-
if self.verbose:
|
|
138
|
-
print(f"✓ Stack detected: {stack}")
|
|
139
|
-
|
|
140
|
-
def check_project_structure(self):
|
|
141
|
-
"""Check for proper project structure and organization."""
|
|
142
|
-
# Check for src/ or app/ directory
|
|
143
|
-
has_src = (self.target_path / 'src').is_dir()
|
|
144
|
-
has_app = (self.target_path / 'app').is_dir()
|
|
145
|
-
has_lib = (self.target_path / 'lib').is_dir()
|
|
146
|
-
|
|
147
|
-
if not has_src and not has_app:
|
|
148
|
-
self._add_finding('warning', 'Missing organized source directory (src/ or app/)')
|
|
149
|
-
|
|
150
|
-
# Check for proper separation of concerns
|
|
151
|
-
if has_src:
|
|
152
|
-
src = self.target_path / 'src'
|
|
153
|
-
subdirs = [d.name for d in src.iterdir() if d.is_dir()]
|
|
154
|
-
expected = {'routes', 'handlers', 'services', 'middleware', 'lib', 'utils', 'db', 'models'}
|
|
155
|
-
found = set(subdirs) & expected
|
|
156
|
-
if len(found) < 2:
|
|
157
|
-
self._add_finding('info', f'Consider organizing src/ with subdirectories: {expected - found}')
|
|
158
|
-
|
|
159
|
-
def check_security_basics(self):
|
|
160
|
-
"""Check for basic security configurations."""
|
|
161
|
-
# Check for .env in .gitignore
|
|
162
|
-
gitignore = self.target_path / '.gitignore'
|
|
163
|
-
if gitignore.exists():
|
|
164
|
-
content = gitignore.read_text()
|
|
165
|
-
if '.env' not in content:
|
|
166
|
-
self._add_finding('critical', '.env is not in .gitignore — secrets may be committed!')
|
|
167
|
-
self.results['score'] -= 20
|
|
168
|
-
else:
|
|
169
|
-
self._add_finding('warning', 'No .gitignore found')
|
|
170
|
-
self.results['score'] -= 10
|
|
171
|
-
|
|
172
|
-
# Check for hardcoded secrets in common config files
|
|
173
|
-
env_file = self.target_path / '.env'
|
|
174
|
-
if env_file.exists():
|
|
175
|
-
self._add_finding('info', '.env file found — ensure it is not committed to version control')
|
|
176
|
-
|
|
177
|
-
# Check for .env.example
|
|
178
|
-
env_example = self.target_path / '.env.example'
|
|
179
|
-
if not env_example.exists():
|
|
180
|
-
self._add_finding('warning', 'No .env.example found — document required environment variables')
|
|
181
|
-
|
|
182
|
-
def check_database_patterns(self):
|
|
183
|
-
"""Check for database best practices."""
|
|
184
|
-
# Check for migration files
|
|
185
|
-
migration_dirs = ['migrations', 'prisma/migrations', 'drizzle', 'alembic']
|
|
186
|
-
has_migrations = any((self.target_path / d).is_dir() for d in migration_dirs)
|
|
187
|
-
|
|
188
|
-
if not has_migrations and self.results['stack'].get('database'):
|
|
189
|
-
self._add_finding('warning', 'No database migration directory found — use managed migrations')
|
|
190
|
-
self.results['score'] -= 10
|
|
191
|
-
|
|
192
|
-
def check_testing_setup(self):
|
|
193
|
-
"""Check for testing configuration."""
|
|
194
|
-
test_configs = [
|
|
195
|
-
'vitest.config.ts', 'vitest.config.js',
|
|
196
|
-
'jest.config.ts', 'jest.config.js',
|
|
197
|
-
'playwright.config.ts', 'pytest.ini', 'conftest.py',
|
|
198
|
-
]
|
|
199
|
-
has_tests = any((self.target_path / c).exists() for c in test_configs)
|
|
200
|
-
|
|
201
|
-
test_dirs = ['__tests__', 'tests', 'test', 'e2e', 'spec']
|
|
202
|
-
has_test_dir = any((self.target_path / d).is_dir() for d in test_dirs)
|
|
203
|
-
|
|
204
|
-
if not has_tests and not has_test_dir:
|
|
205
|
-
self._add_finding('warning', 'No testing setup detected — add unit and integration tests')
|
|
206
|
-
self.results['score'] -= 15
|
|
207
|
-
|
|
208
|
-
def check_ci_cd(self):
|
|
209
|
-
"""Check for CI/CD pipeline configuration."""
|
|
210
|
-
ci_paths = [
|
|
211
|
-
'.github/workflows',
|
|
212
|
-
'.gitlab-ci.yml',
|
|
213
|
-
'.circleci',
|
|
214
|
-
'Jenkinsfile',
|
|
215
|
-
'.drone.yml',
|
|
216
|
-
]
|
|
217
|
-
has_ci = any(
|
|
218
|
-
(self.target_path / p).exists() or (self.target_path / p).is_dir()
|
|
219
|
-
for p in ci_paths
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
if not has_ci:
|
|
223
|
-
self._add_finding('info', 'No CI/CD pipeline detected — consider adding automated checks')
|
|
224
|
-
|
|
225
|
-
def check_docker_setup(self):
|
|
226
|
-
"""Check Docker configuration best practices."""
|
|
227
|
-
dockerfile = self.target_path / 'Dockerfile'
|
|
228
|
-
if dockerfile.exists():
|
|
229
|
-
content = dockerfile.read_text()
|
|
230
|
-
if 'FROM' in content:
|
|
231
|
-
# Check for multi-stage build
|
|
232
|
-
from_count = content.count('FROM ')
|
|
233
|
-
if from_count < 2:
|
|
234
|
-
self._add_finding('info', 'Consider using multi-stage Docker builds for smaller images')
|
|
235
|
-
|
|
236
|
-
# Check for non-root user
|
|
237
|
-
if 'USER ' not in content and 'useradd' not in content and 'adduser' not in content:
|
|
238
|
-
self._add_finding('warning', 'Dockerfile runs as root — add a non-root USER')
|
|
239
|
-
self.results['score'] -= 5
|
|
240
|
-
|
|
241
|
-
# Check for HEALTHCHECK
|
|
242
|
-
if 'HEALTHCHECK' not in content:
|
|
243
|
-
self._add_finding('info', 'Consider adding HEALTHCHECK to Dockerfile')
|
|
244
|
-
|
|
245
|
-
def _add_finding(self, severity: str, message: str):
|
|
246
|
-
"""Add a finding to the results."""
|
|
247
|
-
self.results['findings'].append({
|
|
248
|
-
'severity': severity,
|
|
249
|
-
'message': message,
|
|
250
|
-
})
|
|
251
|
-
if self.verbose:
|
|
252
|
-
icon = {'critical': '🔴', 'warning': '🟡', 'info': '🔵'}.get(severity, '⚪')
|
|
253
|
-
print(f" {icon} [{severity.upper()}] {message}")
|
|
254
|
-
|
|
255
|
-
def generate_report(self):
|
|
256
|
-
"""Generate and display the analysis report."""
|
|
257
|
-
findings = self.results['findings']
|
|
258
|
-
critical = len([f for f in findings if f['severity'] == 'critical'])
|
|
259
|
-
warnings = len([f for f in findings if f['severity'] == 'warning'])
|
|
260
|
-
info = len([f for f in findings if f['severity'] == 'info'])
|
|
261
|
-
|
|
262
|
-
score = max(0, self.results['score'])
|
|
263
|
-
self.results['score'] = score
|
|
264
|
-
|
|
265
|
-
print(f"\n{'='*60}")
|
|
266
|
-
print("🏗️ ARCHITECTURE ANALYSIS REPORT")
|
|
267
|
-
print(f"{'='*60}")
|
|
268
|
-
print(f"Target: {self.results['target']}")
|
|
269
|
-
print(f"Score: {score}/100")
|
|
270
|
-
print(f"Findings: {critical} critical, {warnings} warnings, {info} info")
|
|
271
|
-
print(f"\nStack Detected:")
|
|
272
|
-
for layer, techs in self.results['stack'].items():
|
|
273
|
-
if techs:
|
|
274
|
-
print(f" {layer}: {', '.join(techs)}")
|
|
275
|
-
print(f"\nFindings:")
|
|
276
|
-
for f in findings:
|
|
277
|
-
icon = {'critical': '🔴', 'warning': '🟡', 'info': '🔵'}.get(f['severity'], '⚪')
|
|
278
|
-
print(f" {icon} [{f['severity'].upper()}] {f['message']}")
|
|
279
|
-
print(f"{'='*60}\n")
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def main():
|
|
283
|
-
"""Main entry point."""
|
|
284
|
-
parser = argparse.ArgumentParser(
|
|
285
|
-
description="Fullstack Architecture Analyzer — detects anti-patterns and validates best practices"
|
|
286
|
-
)
|
|
287
|
-
parser.add_argument(
|
|
288
|
-
'target',
|
|
289
|
-
help='Target project path to analyze'
|
|
290
|
-
)
|
|
291
|
-
parser.add_argument(
|
|
292
|
-
'--verbose', '-v',
|
|
293
|
-
action='store_true',
|
|
294
|
-
help='Enable verbose output'
|
|
295
|
-
)
|
|
296
|
-
parser.add_argument(
|
|
297
|
-
'--json',
|
|
298
|
-
action='store_true',
|
|
299
|
-
help='Output results as JSON'
|
|
300
|
-
)
|
|
301
|
-
parser.add_argument(
|
|
302
|
-
'--output', '-o',
|
|
303
|
-
help='Output file path'
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
args = parser.parse_args()
|
|
307
|
-
|
|
308
|
-
analyzer = ArchitectureAnalyzer(
|
|
309
|
-
args.target,
|
|
310
|
-
verbose=args.verbose
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
results = analyzer.run()
|
|
314
|
-
|
|
315
|
-
if args.json:
|
|
316
|
-
output = json.dumps(results, indent=2)
|
|
317
|
-
if args.output:
|
|
318
|
-
with open(args.output, 'w') as f:
|
|
319
|
-
f.write(output)
|
|
320
|
-
print(f"Results written to {args.output}")
|
|
321
|
-
else:
|
|
322
|
-
print(output)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if __name__ == '__main__':
|
|
326
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Fullstack Architecture Analyzer
|
|
4
|
+
Analyzes fullstack project structure, detects anti-patterns, and validates architecture health.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import argparse
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArchitectureAnalyzer:
|
|
16
|
+
"""Analyzes fullstack project architecture for anti-patterns and best practices."""
|
|
17
|
+
|
|
18
|
+
# Common fullstack project indicators
|
|
19
|
+
FRONTEND_MARKERS = {
|
|
20
|
+
'package.json', 'next.config.js', 'next.config.ts', 'next.config.mjs',
|
|
21
|
+
'vite.config.ts', 'vite.config.js', 'nuxt.config.ts', 'svelte.config.js',
|
|
22
|
+
'astro.config.mjs',
|
|
23
|
+
}
|
|
24
|
+
BACKEND_MARKERS = {
|
|
25
|
+
'requirements.txt', 'pyproject.toml', 'go.mod', 'Cargo.toml',
|
|
26
|
+
'pom.xml', 'build.gradle', 'Gemfile',
|
|
27
|
+
}
|
|
28
|
+
INFRA_MARKERS = {
|
|
29
|
+
'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
|
|
30
|
+
'terraform.tf', 'main.tf', 'pulumi.yaml', 'serverless.yml',
|
|
31
|
+
'k8s/', 'kubernetes/', 'helm/',
|
|
32
|
+
}
|
|
33
|
+
CONFIG_MARKERS = {
|
|
34
|
+
'.env', '.env.example', '.env.local',
|
|
35
|
+
'tsconfig.json', 'eslint.config.js', '.eslintrc.js',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def __init__(self, target_path: str, verbose: bool = False):
|
|
39
|
+
self.target_path = Path(target_path)
|
|
40
|
+
self.verbose = verbose
|
|
41
|
+
self.results: Dict = {
|
|
42
|
+
'status': 'pending',
|
|
43
|
+
'target': str(self.target_path),
|
|
44
|
+
'stack': {},
|
|
45
|
+
'findings': [],
|
|
46
|
+
'score': 100,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def run(self) -> Dict:
|
|
50
|
+
"""Execute the full architecture analysis."""
|
|
51
|
+
print(f"🏗️ Running Architecture Analyzer...")
|
|
52
|
+
print(f"📁 Target: {self.target_path}")
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
self.validate_target()
|
|
56
|
+
self.detect_stack()
|
|
57
|
+
self.check_project_structure()
|
|
58
|
+
self.check_security_basics()
|
|
59
|
+
self.check_database_patterns()
|
|
60
|
+
self.check_testing_setup()
|
|
61
|
+
self.check_ci_cd()
|
|
62
|
+
self.check_docker_setup()
|
|
63
|
+
self.generate_report()
|
|
64
|
+
|
|
65
|
+
self.results['status'] = 'success'
|
|
66
|
+
print("✅ Analysis completed!")
|
|
67
|
+
return self.results
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"❌ Error: {e}")
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
def validate_target(self):
|
|
74
|
+
"""Validate the target path exists."""
|
|
75
|
+
if not self.target_path.exists():
|
|
76
|
+
raise ValueError(f"Target path does not exist: {self.target_path}")
|
|
77
|
+
if self.verbose:
|
|
78
|
+
print(f"✓ Target validated: {self.target_path}")
|
|
79
|
+
|
|
80
|
+
def detect_stack(self):
|
|
81
|
+
"""Detect the technology stack used in the project."""
|
|
82
|
+
stack = {'frontend': [], 'backend': [], 'infrastructure': [], 'database': []}
|
|
83
|
+
|
|
84
|
+
# Check for frontend frameworks
|
|
85
|
+
if (self.target_path / 'next.config.js').exists() or \
|
|
86
|
+
(self.target_path / 'next.config.ts').exists() or \
|
|
87
|
+
(self.target_path / 'next.config.mjs').exists():
|
|
88
|
+
stack['frontend'].append('Next.js')
|
|
89
|
+
|
|
90
|
+
if (self.target_path / 'vite.config.ts').exists() or \
|
|
91
|
+
(self.target_path / 'vite.config.js').exists():
|
|
92
|
+
stack['frontend'].append('Vite')
|
|
93
|
+
|
|
94
|
+
if (self.target_path / 'nuxt.config.ts').exists():
|
|
95
|
+
stack['frontend'].append('Nuxt')
|
|
96
|
+
|
|
97
|
+
# Check for backend languages
|
|
98
|
+
if (self.target_path / 'go.mod').exists():
|
|
99
|
+
stack['backend'].append('Go')
|
|
100
|
+
if (self.target_path / 'Cargo.toml').exists():
|
|
101
|
+
stack['backend'].append('Rust')
|
|
102
|
+
if (self.target_path / 'pyproject.toml').exists() or \
|
|
103
|
+
(self.target_path / 'requirements.txt').exists():
|
|
104
|
+
stack['backend'].append('Python')
|
|
105
|
+
|
|
106
|
+
# Check package.json for more details
|
|
107
|
+
pkg_json = self.target_path / 'package.json'
|
|
108
|
+
if pkg_json.exists():
|
|
109
|
+
try:
|
|
110
|
+
with open(pkg_json) as f:
|
|
111
|
+
pkg = json.load(f)
|
|
112
|
+
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
|
113
|
+
|
|
114
|
+
if 'drizzle-orm' in deps:
|
|
115
|
+
stack['database'].append('Drizzle ORM')
|
|
116
|
+
if 'prisma' in deps or '@prisma/client' in deps:
|
|
117
|
+
stack['database'].append('Prisma')
|
|
118
|
+
if 'hono' in deps:
|
|
119
|
+
stack['backend'].append('Hono')
|
|
120
|
+
if 'fastify' in deps:
|
|
121
|
+
stack['backend'].append('Fastify')
|
|
122
|
+
if 'express' in deps:
|
|
123
|
+
stack['backend'].append('Express')
|
|
124
|
+
except (json.JSONDecodeError, IOError):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
# Check for infrastructure
|
|
128
|
+
if (self.target_path / 'Dockerfile').exists():
|
|
129
|
+
stack['infrastructure'].append('Docker')
|
|
130
|
+
if (self.target_path / 'docker-compose.yml').exists() or \
|
|
131
|
+
(self.target_path / 'docker-compose.yaml').exists():
|
|
132
|
+
stack['infrastructure'].append('Docker Compose')
|
|
133
|
+
if (self.target_path / 'main.tf').exists():
|
|
134
|
+
stack['infrastructure'].append('Terraform')
|
|
135
|
+
|
|
136
|
+
self.results['stack'] = stack
|
|
137
|
+
if self.verbose:
|
|
138
|
+
print(f"✓ Stack detected: {stack}")
|
|
139
|
+
|
|
140
|
+
def check_project_structure(self):
|
|
141
|
+
"""Check for proper project structure and organization."""
|
|
142
|
+
# Check for src/ or app/ directory
|
|
143
|
+
has_src = (self.target_path / 'src').is_dir()
|
|
144
|
+
has_app = (self.target_path / 'app').is_dir()
|
|
145
|
+
has_lib = (self.target_path / 'lib').is_dir()
|
|
146
|
+
|
|
147
|
+
if not has_src and not has_app:
|
|
148
|
+
self._add_finding('warning', 'Missing organized source directory (src/ or app/)')
|
|
149
|
+
|
|
150
|
+
# Check for proper separation of concerns
|
|
151
|
+
if has_src:
|
|
152
|
+
src = self.target_path / 'src'
|
|
153
|
+
subdirs = [d.name for d in src.iterdir() if d.is_dir()]
|
|
154
|
+
expected = {'routes', 'handlers', 'services', 'middleware', 'lib', 'utils', 'db', 'models'}
|
|
155
|
+
found = set(subdirs) & expected
|
|
156
|
+
if len(found) < 2:
|
|
157
|
+
self._add_finding('info', f'Consider organizing src/ with subdirectories: {expected - found}')
|
|
158
|
+
|
|
159
|
+
def check_security_basics(self):
|
|
160
|
+
"""Check for basic security configurations."""
|
|
161
|
+
# Check for .env in .gitignore
|
|
162
|
+
gitignore = self.target_path / '.gitignore'
|
|
163
|
+
if gitignore.exists():
|
|
164
|
+
content = gitignore.read_text()
|
|
165
|
+
if '.env' not in content:
|
|
166
|
+
self._add_finding('critical', '.env is not in .gitignore — secrets may be committed!')
|
|
167
|
+
self.results['score'] -= 20
|
|
168
|
+
else:
|
|
169
|
+
self._add_finding('warning', 'No .gitignore found')
|
|
170
|
+
self.results['score'] -= 10
|
|
171
|
+
|
|
172
|
+
# Check for hardcoded secrets in common config files
|
|
173
|
+
env_file = self.target_path / '.env'
|
|
174
|
+
if env_file.exists():
|
|
175
|
+
self._add_finding('info', '.env file found — ensure it is not committed to version control')
|
|
176
|
+
|
|
177
|
+
# Check for .env.example
|
|
178
|
+
env_example = self.target_path / '.env.example'
|
|
179
|
+
if not env_example.exists():
|
|
180
|
+
self._add_finding('warning', 'No .env.example found — document required environment variables')
|
|
181
|
+
|
|
182
|
+
def check_database_patterns(self):
|
|
183
|
+
"""Check for database best practices."""
|
|
184
|
+
# Check for migration files
|
|
185
|
+
migration_dirs = ['migrations', 'prisma/migrations', 'drizzle', 'alembic']
|
|
186
|
+
has_migrations = any((self.target_path / d).is_dir() for d in migration_dirs)
|
|
187
|
+
|
|
188
|
+
if not has_migrations and self.results['stack'].get('database'):
|
|
189
|
+
self._add_finding('warning', 'No database migration directory found — use managed migrations')
|
|
190
|
+
self.results['score'] -= 10
|
|
191
|
+
|
|
192
|
+
def check_testing_setup(self):
|
|
193
|
+
"""Check for testing configuration."""
|
|
194
|
+
test_configs = [
|
|
195
|
+
'vitest.config.ts', 'vitest.config.js',
|
|
196
|
+
'jest.config.ts', 'jest.config.js',
|
|
197
|
+
'playwright.config.ts', 'pytest.ini', 'conftest.py',
|
|
198
|
+
]
|
|
199
|
+
has_tests = any((self.target_path / c).exists() for c in test_configs)
|
|
200
|
+
|
|
201
|
+
test_dirs = ['__tests__', 'tests', 'test', 'e2e', 'spec']
|
|
202
|
+
has_test_dir = any((self.target_path / d).is_dir() for d in test_dirs)
|
|
203
|
+
|
|
204
|
+
if not has_tests and not has_test_dir:
|
|
205
|
+
self._add_finding('warning', 'No testing setup detected — add unit and integration tests')
|
|
206
|
+
self.results['score'] -= 15
|
|
207
|
+
|
|
208
|
+
def check_ci_cd(self):
|
|
209
|
+
"""Check for CI/CD pipeline configuration."""
|
|
210
|
+
ci_paths = [
|
|
211
|
+
'.github/workflows',
|
|
212
|
+
'.gitlab-ci.yml',
|
|
213
|
+
'.circleci',
|
|
214
|
+
'Jenkinsfile',
|
|
215
|
+
'.drone.yml',
|
|
216
|
+
]
|
|
217
|
+
has_ci = any(
|
|
218
|
+
(self.target_path / p).exists() or (self.target_path / p).is_dir()
|
|
219
|
+
for p in ci_paths
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if not has_ci:
|
|
223
|
+
self._add_finding('info', 'No CI/CD pipeline detected — consider adding automated checks')
|
|
224
|
+
|
|
225
|
+
def check_docker_setup(self):
|
|
226
|
+
"""Check Docker configuration best practices."""
|
|
227
|
+
dockerfile = self.target_path / 'Dockerfile'
|
|
228
|
+
if dockerfile.exists():
|
|
229
|
+
content = dockerfile.read_text()
|
|
230
|
+
if 'FROM' in content:
|
|
231
|
+
# Check for multi-stage build
|
|
232
|
+
from_count = content.count('FROM ')
|
|
233
|
+
if from_count < 2:
|
|
234
|
+
self._add_finding('info', 'Consider using multi-stage Docker builds for smaller images')
|
|
235
|
+
|
|
236
|
+
# Check for non-root user
|
|
237
|
+
if 'USER ' not in content and 'useradd' not in content and 'adduser' not in content:
|
|
238
|
+
self._add_finding('warning', 'Dockerfile runs as root — add a non-root USER')
|
|
239
|
+
self.results['score'] -= 5
|
|
240
|
+
|
|
241
|
+
# Check for HEALTHCHECK
|
|
242
|
+
if 'HEALTHCHECK' not in content:
|
|
243
|
+
self._add_finding('info', 'Consider adding HEALTHCHECK to Dockerfile')
|
|
244
|
+
|
|
245
|
+
def _add_finding(self, severity: str, message: str):
|
|
246
|
+
"""Add a finding to the results."""
|
|
247
|
+
self.results['findings'].append({
|
|
248
|
+
'severity': severity,
|
|
249
|
+
'message': message,
|
|
250
|
+
})
|
|
251
|
+
if self.verbose:
|
|
252
|
+
icon = {'critical': '🔴', 'warning': '🟡', 'info': '🔵'}.get(severity, '⚪')
|
|
253
|
+
print(f" {icon} [{severity.upper()}] {message}")
|
|
254
|
+
|
|
255
|
+
def generate_report(self):
|
|
256
|
+
"""Generate and display the analysis report."""
|
|
257
|
+
findings = self.results['findings']
|
|
258
|
+
critical = len([f for f in findings if f['severity'] == 'critical'])
|
|
259
|
+
warnings = len([f for f in findings if f['severity'] == 'warning'])
|
|
260
|
+
info = len([f for f in findings if f['severity'] == 'info'])
|
|
261
|
+
|
|
262
|
+
score = max(0, self.results['score'])
|
|
263
|
+
self.results['score'] = score
|
|
264
|
+
|
|
265
|
+
print(f"\n{'='*60}")
|
|
266
|
+
print("🏗️ ARCHITECTURE ANALYSIS REPORT")
|
|
267
|
+
print(f"{'='*60}")
|
|
268
|
+
print(f"Target: {self.results['target']}")
|
|
269
|
+
print(f"Score: {score}/100")
|
|
270
|
+
print(f"Findings: {critical} critical, {warnings} warnings, {info} info")
|
|
271
|
+
print(f"\nStack Detected:")
|
|
272
|
+
for layer, techs in self.results['stack'].items():
|
|
273
|
+
if techs:
|
|
274
|
+
print(f" {layer}: {', '.join(techs)}")
|
|
275
|
+
print(f"\nFindings:")
|
|
276
|
+
for f in findings:
|
|
277
|
+
icon = {'critical': '🔴', 'warning': '🟡', 'info': '🔵'}.get(f['severity'], '⚪')
|
|
278
|
+
print(f" {icon} [{f['severity'].upper()}] {f['message']}")
|
|
279
|
+
print(f"{'='*60}\n")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def main():
|
|
283
|
+
"""Main entry point."""
|
|
284
|
+
parser = argparse.ArgumentParser(
|
|
285
|
+
description="Fullstack Architecture Analyzer — detects anti-patterns and validates best practices"
|
|
286
|
+
)
|
|
287
|
+
parser.add_argument(
|
|
288
|
+
'target',
|
|
289
|
+
help='Target project path to analyze'
|
|
290
|
+
)
|
|
291
|
+
parser.add_argument(
|
|
292
|
+
'--verbose', '-v',
|
|
293
|
+
action='store_true',
|
|
294
|
+
help='Enable verbose output'
|
|
295
|
+
)
|
|
296
|
+
parser.add_argument(
|
|
297
|
+
'--json',
|
|
298
|
+
action='store_true',
|
|
299
|
+
help='Output results as JSON'
|
|
300
|
+
)
|
|
301
|
+
parser.add_argument(
|
|
302
|
+
'--output', '-o',
|
|
303
|
+
help='Output file path'
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
args = parser.parse_args()
|
|
307
|
+
|
|
308
|
+
analyzer = ArchitectureAnalyzer(
|
|
309
|
+
args.target,
|
|
310
|
+
verbose=args.verbose
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
results = analyzer.run()
|
|
314
|
+
|
|
315
|
+
if args.json:
|
|
316
|
+
output = json.dumps(results, indent=2)
|
|
317
|
+
if args.output:
|
|
318
|
+
with open(args.output, 'w') as f:
|
|
319
|
+
f.write(output)
|
|
320
|
+
print(f"Results written to {args.output}")
|
|
321
|
+
else:
|
|
322
|
+
print(output)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
if __name__ == '__main__':
|
|
326
|
+
main()
|