archflow-mcp / scan_project.py
Ryan Cashman
Simplify to pure HTML - remove rich dep
a089044
#!/usr/bin/env python3
"""
ArchFlow Project Scanner
Run this in your project directory to generate a scan report for ArchFlow analysis.
Usage:
python scan_project.py
Or copy this URL and run in your terminal:
curl -sSL https://archflow.ai/scan.py | python3
"""
import os
import json
import sys
from pathlib import Path
from typing import Dict, List, Any
def scan_directory_structure(root_path: Path, max_depth: int = 3) -> Dict[str, Any]:
"""Scan directory structure"""
structure = {
"directories": [],
"files": [],
"total_files": 0,
"total_dirs": 0
}
try:
for item in root_path.rglob("*"):
if item.is_dir():
# Skip common ignore patterns
if any(skip in str(item) for skip in [".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build"]):
continue
structure["directories"].append(str(item.relative_to(root_path)))
structure["total_dirs"] += 1
elif item.is_file():
structure["files"].append(str(item.relative_to(root_path)))
structure["total_files"] += 1
except Exception as e:
structure["error"] = str(e)
return structure
def detect_technologies(root_path: Path) -> Dict[str, List[str]]:
"""Detect technologies used in the project"""
techs = {
"languages": [],
"frameworks": [],
"tools": [],
"infrastructure": []
}
# Check for common config files
config_files = {
"package.json": ("JavaScript/TypeScript", "npm"),
"requirements.txt": ("Python", "pip"),
"Pipfile": ("Python", "pipenv"),
"pyproject.toml": ("Python", "poetry/uv"),
"Cargo.toml": ("Rust", "cargo"),
"go.mod": ("Go", "go modules"),
"pom.xml": ("Java", "Maven"),
"build.gradle": ("Java/Kotlin", "Gradle"),
"Gemfile": ("Ruby", "bundler"),
"composer.json": ("PHP", "composer"),
"Dockerfile": ("Docker", "containerization"),
"docker-compose.yml": ("Docker Compose", "orchestration"),
"vercel.json": ("Vercel", "deployment"),
"netlify.toml": ("Netlify", "deployment"),
".github/workflows": ("GitHub Actions", "CI/CD"),
"terraform": ("Terraform", "IaC"),
"kubernetes": ("Kubernetes", "orchestration"),
"next.config.js": ("Next.js", "React framework"),
"vite.config.js": ("Vite", "build tool"),
"tsconfig.json": ("TypeScript", "language"),
}
for config_file, (tech, category) in config_files.items():
file_path = root_path / config_file
if file_path.exists() or (root_path / config_file.split("/")[0]).is_dir():
if "language" in category.lower() or tech in ["Python", "JavaScript", "TypeScript", "Rust", "Go", "Java", "Ruby", "PHP"]:
techs["languages"].append(tech)
elif "framework" in category.lower() or tech in ["Next.js", "React", "Vue"]:
techs["frameworks"].append(tech)
elif "deploy" in category.lower() or "CI" in category:
techs["infrastructure"].append(f"{tech} ({category})")
else:
techs["tools"].append(f"{tech} ({category})")
# Remove duplicates
for key in techs:
techs[key] = list(set(techs[key]))
return techs
def analyze_package_json(root_path: Path) -> Dict[str, Any]:
"""Analyze package.json for JavaScript/TypeScript projects"""
package_json = root_path / "package.json"
if not package_json.exists():
return {}
try:
with open(package_json) as f:
data = json.load(f)
return {
"dependencies": len(data.get("dependencies", {})),
"devDependencies": len(data.get("devDependencies", {})),
"scripts": list(data.get("scripts", {}).keys()),
"name": data.get("name", "unknown"),
}
except Exception as e:
return {"error": str(e)}
def analyze_requirements(root_path: Path) -> Dict[str, Any]:
"""Analyze requirements.txt for Python projects"""
req_file = root_path / "requirements.txt"
if not req_file.exists():
return {}
try:
with open(req_file) as f:
requirements = [line.strip() for line in f if line.strip() and not line.startswith("#")]
return {
"total_dependencies": len(requirements),
"dependencies": requirements[:10] # First 10
}
except Exception as e:
return {"error": str(e)}
def generate_report(root_path: Path) -> str:
"""Generate the full scan report"""
report_lines = [
"=" * 60,
"ARCHFLOW PROJECT SCAN REPORT",
"=" * 60,
"",
f"πŸ“ Project Path: {root_path.absolute()}",
f"πŸ“Š Scanned: {os.path.basename(root_path)}",
"",
"πŸ” DIRECTORY STRUCTURE",
"-" * 60,
]
# Directory structure
structure = scan_directory_structure(root_path)
report_lines.append(f"Total Directories: {structure['total_dirs']}")
report_lines.append(f"Total Files: {structure['total_files']}")
report_lines.append("")
# Technologies
report_lines.append("πŸ› οΈ DETECTED TECHNOLOGIES")
report_lines.append("-" * 60)
techs = detect_technologies(root_path)
if techs["languages"]:
report_lines.append(f"Languages: {', '.join(techs['languages'])}")
if techs["frameworks"]:
report_lines.append(f"Frameworks: {', '.join(techs['frameworks'])}")
if techs["tools"]:
report_lines.append(f"Tools: {', '.join(techs['tools'])}")
if techs["infrastructure"]:
report_lines.append(f"Infrastructure: {', '.join(techs['infrastructure'])}")
report_lines.append("")
# Package analysis
pkg_data = analyze_package_json(root_path)
if pkg_data:
report_lines.append("πŸ“¦ PACKAGE.JSON ANALYSIS")
report_lines.append("-" * 60)
report_lines.append(f"Project Name: {pkg_data.get('name', 'N/A')}")
report_lines.append(f"Dependencies: {pkg_data.get('dependencies', 0)}")
report_lines.append(f"Dev Dependencies: {pkg_data.get('devDependencies', 0)}")
report_lines.append(f"Scripts: {', '.join(pkg_data.get('scripts', []))}")
report_lines.append("")
# Python requirements
req_data = analyze_requirements(root_path)
if req_data:
report_lines.append("🐍 PYTHON DEPENDENCIES")
report_lines.append("-" * 60)
report_lines.append(f"Total: {req_data.get('total_dependencies', 0)}")
if "dependencies" in req_data:
report_lines.append("Top dependencies:")
for dep in req_data["dependencies"]:
report_lines.append(f" - {dep}")
report_lines.append("")
# Complexity warnings
report_lines.append("⚠️ COMPLEXITY INDICATORS")
report_lines.append("-" * 60)
warnings = []
if "Kubernetes" in str(techs["infrastructure"]):
warnings.append("🚨 Kubernetes detected - Consider if Docker Compose is sufficient")
if structure["total_dirs"] > 50:
warnings.append(f"πŸ“ Large project ({structure['total_dirs']} directories) - Consider modularization")
if pkg_data.get("dependencies", 0) > 50:
warnings.append(f"πŸ“¦ Many dependencies ({pkg_data['dependencies']}) - Bundle size concern")
if warnings:
for warning in warnings:
report_lines.append(warning)
else:
report_lines.append("βœ… No obvious complexity concerns detected")
report_lines.append("")
report_lines.append("=" * 60)
report_lines.append("Copy this entire report and paste it into ArchFlow for analysis!")
report_lines.append("=" * 60)
return "\n".join(report_lines)
def main():
"""Main execution"""
import sys
import io
# Fix Windows encoding for emojis
if sys.platform == "win32":
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
print("\nπŸ–₯️ ArchFlow Project Scanner\n")
# Get project path (current directory by default)
if len(sys.argv) > 1:
project_path = Path(sys.argv[1])
else:
project_path = Path.cwd()
if not project_path.exists():
print(f"❌ Error: Path '{project_path}' does not exist")
sys.exit(1)
print(f"πŸ“‚ Scanning: {project_path.absolute()}")
print("⏳ Please wait...\n")
report = generate_report(project_path)
print(report)
# Optionally save to file
output_file = project_path / "archflow_scan.txt"
try:
with open(output_file, "w", encoding="utf-8") as f:
f.write(report)
print(f"\nπŸ’Ύ Report saved to: {output_file}")
except Exception as e:
print(f"\n⚠️ Could not save report: {e}")
if __name__ == "__main__":
main()