Initial commit
This commit is contained in:
308
skills/scripts/create_package.py
Normal file
308
skills/scripts/create_package.py
Normal file
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Package architecture documentation into a comprehensive ZIP file.
|
||||
Includes: ARCHITECTURE.md, OpenAPI spec, PDF, and diagram images.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import zipfile
|
||||
|
||||
|
||||
def convert_markdown_to_pdf(md_file: str, output_pdf: str, work_dir: str) -> bool:
|
||||
"""Convert markdown to PDF using pandoc if available, else create placeholder."""
|
||||
try:
|
||||
# Try using pandoc
|
||||
result = subprocess.run(
|
||||
['pandoc', md_file, '-o', output_pdf, '--pdf-engine=pdflatex'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return True
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
|
||||
# Fallback: Try weasyprint
|
||||
try:
|
||||
import markdown
|
||||
from weasyprint import HTML, CSS
|
||||
|
||||
# Read markdown
|
||||
with open(md_file, 'r', encoding='utf-8') as f:
|
||||
md_content = f.read()
|
||||
|
||||
# Convert to HTML
|
||||
html_content = markdown.markdown(md_content, extensions=['tables', 'fenced_code'])
|
||||
|
||||
# Add styling
|
||||
styled_html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
padding: 0 20px;
|
||||
}}
|
||||
h1, h2, h3 {{ color: #333; }}
|
||||
h1 {{ border-bottom: 2px solid #333; padding-bottom: 10px; }}
|
||||
h2 {{ border-bottom: 1px solid #666; padding-bottom: 5px; margin-top: 30px; }}
|
||||
code {{
|
||||
background: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}}
|
||||
pre {{
|
||||
background: #f4f4f4;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
}}
|
||||
table {{
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}}
|
||||
th, td {{
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}}
|
||||
th {{
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{html_content}
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Convert HTML to PDF
|
||||
HTML(string=styled_html).write_pdf(output_pdf)
|
||||
return True
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Last resort: Create a simple text-based PDF notice
|
||||
notice = f"""PDF Generation Not Available
|
||||
|
||||
The required tools (pandoc or weasyprint) are not installed.
|
||||
Please refer to the ARCHITECTURE.md file for the full documentation.
|
||||
|
||||
To generate a PDF manually:
|
||||
1. Install pandoc: apt-get install pandoc texlive-latex-base
|
||||
OR
|
||||
2. Install weasyprint: pip install markdown weasyprint --break-system-packages
|
||||
|
||||
Then run:
|
||||
pandoc {md_file} -o {output_pdf}
|
||||
OR
|
||||
python {os.path.abspath(__file__)} <work_dir>
|
||||
"""
|
||||
|
||||
# Create a simple text file as placeholder
|
||||
with open(output_pdf + '.txt', 'w') as f:
|
||||
f.write(notice)
|
||||
|
||||
print(f"⚠️ PDF generation tools not available. Created notice file instead.")
|
||||
return False
|
||||
|
||||
|
||||
def render_mermaid_diagrams(work_dir: str, diagrams_dir: str) -> list:
|
||||
"""Render Mermaid diagrams to PNG images using mmdc if available."""
|
||||
|
||||
# Find all .mmd files in work directory
|
||||
mmd_files = list(Path(work_dir).glob('*.mmd'))
|
||||
|
||||
if not mmd_files:
|
||||
print("No .mmd files found to render")
|
||||
return []
|
||||
|
||||
os.makedirs(diagrams_dir, exist_ok=True)
|
||||
rendered_files = []
|
||||
|
||||
for mmd_file in mmd_files:
|
||||
output_file = os.path.join(diagrams_dir, mmd_file.stem + '.png')
|
||||
|
||||
try:
|
||||
# Try using mermaid-cli (mmdc)
|
||||
result = subprocess.run(
|
||||
['mmdc', '-i', str(mmd_file), '-o', output_file, '-b', 'transparent'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode == 0 and os.path.exists(output_file):
|
||||
rendered_files.append(output_file)
|
||||
print(f"✅ Rendered: {mmd_file.name} → {os.path.basename(output_file)}")
|
||||
else:
|
||||
print(f"⚠️ Could not render: {mmd_file.name}")
|
||||
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired) as e:
|
||||
print(f"⚠️ mermaid-cli not available. Skipping diagram rendering.")
|
||||
print(f" Install with: npm install -g @mermaid-js/mermaid-cli")
|
||||
break
|
||||
|
||||
return rendered_files
|
||||
|
||||
|
||||
def create_package_zip(work_dir: str, output_zip: str) -> bool:
|
||||
"""Create a ZIP package with all documentation files in proper structure."""
|
||||
|
||||
work_path = Path(work_dir)
|
||||
|
||||
# Collect files to include
|
||||
files_to_include = []
|
||||
|
||||
# ARCHITECTURE.md (required)
|
||||
arch_file = work_path / 'ARCHITECTURE.md'
|
||||
if arch_file.exists():
|
||||
files_to_include.append(('ARCHITECTURE.md', arch_file))
|
||||
else:
|
||||
print("❌ ARCHITECTURE.md not found!")
|
||||
return False
|
||||
|
||||
# OpenAPI spec (if exists)
|
||||
openapi_file = work_path / 'openapi.json'
|
||||
if openapi_file.exists():
|
||||
files_to_include.append(('openapi.json', openapi_file))
|
||||
|
||||
# PDF (if exists)
|
||||
pdf_file = work_path / 'ARCHITECTURE.pdf'
|
||||
pdf_exists = False
|
||||
if pdf_file.exists():
|
||||
files_to_include.append(('ARCHITECTURE.pdf', pdf_file))
|
||||
pdf_exists = True
|
||||
else:
|
||||
# Check for the txt notice
|
||||
pdf_txt = work_path / 'ARCHITECTURE.pdf.txt'
|
||||
if pdf_txt.exists():
|
||||
files_to_include.append(('PDF_GENERATION_NOTICE.txt', pdf_txt))
|
||||
|
||||
# Find .mmd source files (should be in work_dir root)
|
||||
mmd_files = sorted(list(work_path.glob('*.mmd')))
|
||||
|
||||
# Find diagram images (PNG/SVG)
|
||||
diagrams_dir = work_path / 'diagrams'
|
||||
diagram_files = []
|
||||
if diagrams_dir.exists():
|
||||
diagram_files = sorted(list(diagrams_dir.glob('*.png')) + list(diagrams_dir.glob('*.svg')))
|
||||
|
||||
# Create ZIP with proper structure
|
||||
try:
|
||||
with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
# Add main files at root
|
||||
for arc_name, file_path in files_to_include:
|
||||
zipf.write(file_path, arc_name)
|
||||
print(f"📦 Added: {arc_name}")
|
||||
|
||||
# Add .mmd source files to diagrams/source/
|
||||
if mmd_files:
|
||||
for mmd_file in mmd_files:
|
||||
arc_name = f"diagrams/source/{mmd_file.name}"
|
||||
zipf.write(mmd_file, arc_name)
|
||||
print(f"📦 Added: {arc_name}")
|
||||
|
||||
# Add rendered diagram images to diagrams/
|
||||
if diagram_files:
|
||||
for diagram_file in diagram_files:
|
||||
arc_name = f"diagrams/{diagram_file.name}"
|
||||
zipf.write(diagram_file, arc_name)
|
||||
print(f"📦 Added: {arc_name}")
|
||||
|
||||
# Print summary
|
||||
print(f"\n✅ Package created: {output_zip}")
|
||||
print(f"\n📦 Package contents:")
|
||||
print(f" ├── ARCHITECTURE.md")
|
||||
if pdf_exists:
|
||||
print(f" ├── ARCHITECTURE.pdf")
|
||||
if openapi_file.exists():
|
||||
print(f" ├── openapi.json")
|
||||
if mmd_files or diagram_files:
|
||||
print(f" └── diagrams/")
|
||||
if diagram_files:
|
||||
for df in diagram_files:
|
||||
print(f" ├── {df.name}")
|
||||
if mmd_files:
|
||||
print(f" └── source/")
|
||||
for mf in mmd_files:
|
||||
print(f" ├── {mf.name}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error creating ZIP: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python create_package.py <work_directory> [output_zip]")
|
||||
print("\nThe work directory should contain:")
|
||||
print(" - ARCHITECTURE.md (required)")
|
||||
print(" - openapi.json (optional)")
|
||||
print(" - *.mmd files (optional, for diagram rendering)")
|
||||
sys.exit(1)
|
||||
|
||||
work_dir = sys.argv[1]
|
||||
output_zip = sys.argv[2] if len(sys.argv) > 2 else os.path.join(work_dir, 'architecture-package.zip')
|
||||
|
||||
if not os.path.exists(work_dir):
|
||||
print(f"❌ Work directory not found: {work_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"📦 Creating architecture documentation package...\n")
|
||||
print(f"Work directory: {work_dir}")
|
||||
print(f"Output ZIP: {output_zip}\n")
|
||||
|
||||
# Step 1: Convert Markdown to PDF
|
||||
arch_md = os.path.join(work_dir, 'ARCHITECTURE.md')
|
||||
if os.path.exists(arch_md):
|
||||
print("📄 Converting ARCHITECTURE.md to PDF...")
|
||||
pdf_file = os.path.join(work_dir, 'ARCHITECTURE.pdf')
|
||||
convert_markdown_to_pdf(arch_md, pdf_file, work_dir)
|
||||
print()
|
||||
|
||||
# Step 2: Render Mermaid diagrams
|
||||
diagrams_dir = os.path.join(work_dir, 'diagrams')
|
||||
print("🎨 Rendering Mermaid diagrams...")
|
||||
rendered = render_mermaid_diagrams(work_dir, diagrams_dir)
|
||||
if rendered:
|
||||
print(f"✅ Rendered {len(rendered)} diagram(s)")
|
||||
print()
|
||||
|
||||
# Step 3: Create ZIP package
|
||||
print("📦 Creating ZIP package...")
|
||||
success = create_package_zip(work_dir, output_zip)
|
||||
|
||||
if success:
|
||||
# Get file size
|
||||
size_mb = os.path.getsize(output_zip) / (1024 * 1024)
|
||||
print(f"\n✅ Package complete! Size: {size_mb:.2f} MB")
|
||||
print(f"📦 {output_zip}")
|
||||
else:
|
||||
print("\n❌ Package creation failed")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
205
skills/scripts/generate_diagram.py
Normal file
205
skills/scripts/generate_diagram.py
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate ASCII/text-based system architecture diagrams.
|
||||
Supports simple component and data flow diagrams.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
|
||||
def generate_simple_diagram(components: List[str], connections: List[Tuple[str, str]]) -> str:
|
||||
"""
|
||||
Generate a simple left-to-right component diagram.
|
||||
|
||||
Args:
|
||||
components: List of component names
|
||||
connections: List of (from_component, to_component) tuples
|
||||
"""
|
||||
diagram_lines = []
|
||||
|
||||
# Create component boxes
|
||||
for i, component in enumerate(components):
|
||||
if i == 0:
|
||||
diagram_lines.append(f"[{component}]")
|
||||
else:
|
||||
# Add connection arrow
|
||||
prev = components[i-1]
|
||||
if (prev, component) in connections or (component, prev) in connections:
|
||||
diagram_lines.append(" |")
|
||||
diagram_lines.append(" v")
|
||||
else:
|
||||
diagram_lines.append("")
|
||||
diagram_lines.append(f"[{component}]")
|
||||
|
||||
return "\n".join(diagram_lines)
|
||||
|
||||
|
||||
def generate_layered_diagram(layers: Dict[str, List[str]]) -> str:
|
||||
"""
|
||||
Generate a layered architecture diagram.
|
||||
|
||||
Args:
|
||||
layers: Dict of layer_name -> [components]
|
||||
"""
|
||||
diagram_lines = []
|
||||
|
||||
for layer_name, components in layers.items():
|
||||
diagram_lines.append(f"\n{layer_name}:")
|
||||
diagram_lines.append("+" + "-" * 60 + "+")
|
||||
|
||||
for component in components:
|
||||
# Center the component name
|
||||
padding = (58 - len(component)) // 2
|
||||
diagram_lines.append(f"| {' ' * padding}{component}{' ' * (58 - len(component) - padding)} |")
|
||||
|
||||
diagram_lines.append("+" + "-" * 60 + "+")
|
||||
diagram_lines.append(" |")
|
||||
diagram_lines.append(" v")
|
||||
|
||||
# Remove last arrow
|
||||
if diagram_lines:
|
||||
diagram_lines = diagram_lines[:-2]
|
||||
|
||||
return "\n".join(diagram_lines)
|
||||
|
||||
|
||||
def generate_flow_diagram(flow: List[Dict[str, str]]) -> str:
|
||||
"""
|
||||
Generate a data flow diagram.
|
||||
|
||||
Args:
|
||||
flow: List of dicts with 'from', 'to', 'label' keys
|
||||
"""
|
||||
diagram_lines = []
|
||||
components_seen = set()
|
||||
|
||||
for step in flow:
|
||||
from_comp = step['from']
|
||||
to_comp = step['to']
|
||||
label = step.get('label', '')
|
||||
|
||||
if from_comp not in components_seen:
|
||||
diagram_lines.append(f"[{from_comp}]")
|
||||
components_seen.add(from_comp)
|
||||
|
||||
# Add arrow with label
|
||||
arrow = f" |--{label}-->" if label else " |---->"
|
||||
diagram_lines.append(arrow)
|
||||
diagram_lines.append(f"[{to_comp}]")
|
||||
components_seen.add(to_comp)
|
||||
|
||||
return "\n".join(diagram_lines)
|
||||
|
||||
|
||||
def generate_c4_context_diagram(system: str, actors: List[str], external_systems: List[str]) -> str:
|
||||
"""
|
||||
Generate a C4 Level 1 (System Context) diagram.
|
||||
|
||||
Args:
|
||||
system: Name of the system being documented
|
||||
actors: List of user types/actors
|
||||
external_systems: List of external systems
|
||||
"""
|
||||
diagram_lines = []
|
||||
|
||||
# Add actors
|
||||
for actor in actors:
|
||||
diagram_lines.append(f"[{actor}]")
|
||||
diagram_lines.append(" |")
|
||||
diagram_lines.append(" v")
|
||||
|
||||
# Add main system
|
||||
diagram_lines.append("+" + "=" * 40 + "+")
|
||||
diagram_lines.append(f"|{system.center(40)}|")
|
||||
diagram_lines.append("+" + "=" * 40 + "+")
|
||||
|
||||
# Add external systems
|
||||
if external_systems:
|
||||
diagram_lines.append(" |")
|
||||
diagram_lines.append(" v")
|
||||
for ext_sys in external_systems:
|
||||
diagram_lines.append(f"[{ext_sys}] (External)")
|
||||
if ext_sys != external_systems[-1]:
|
||||
diagram_lines.append(" |")
|
||||
|
||||
return "\n".join(diagram_lines)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python generate_diagram.py <type> [json_config]")
|
||||
print("\nTypes:")
|
||||
print(" simple - Simple component flow")
|
||||
print(" layered - Layered architecture")
|
||||
print(" flow - Data flow diagram")
|
||||
print(" c4 - C4 context diagram")
|
||||
print("\nExamples:")
|
||||
print(' python generate_diagram.py simple \'{"components": ["User", "API", "DB"], "connections": [["User", "API"], ["API", "DB"]]}\'')
|
||||
print(' python generate_diagram.py layered \'{"Presentation": ["Web UI", "Mobile App"], "Business": ["API Service"], "Data": ["Database"]}\'')
|
||||
sys.exit(1)
|
||||
|
||||
diagram_type = sys.argv[1].lower()
|
||||
|
||||
if diagram_type == "simple":
|
||||
if len(sys.argv) < 3:
|
||||
# Default simple example
|
||||
components = ["User", "Frontend", "Backend", "Database"]
|
||||
connections = [("User", "Frontend"), ("Frontend", "Backend"), ("Backend", "Database")]
|
||||
else:
|
||||
config = json.loads(sys.argv[2])
|
||||
components = config.get('components', [])
|
||||
connections = [tuple(c) for c in config.get('connections', [])]
|
||||
|
||||
print(generate_simple_diagram(components, connections))
|
||||
|
||||
elif diagram_type == "layered":
|
||||
if len(sys.argv) < 3:
|
||||
# Default layered example
|
||||
layers = {
|
||||
"Presentation Layer": ["Web Interface", "Mobile App"],
|
||||
"Application Layer": ["Business Logic", "API Service"],
|
||||
"Data Layer": ["Database", "Cache"]
|
||||
}
|
||||
else:
|
||||
layers = json.loads(sys.argv[2])
|
||||
|
||||
print(generate_layered_diagram(layers))
|
||||
|
||||
elif diagram_type == "flow":
|
||||
if len(sys.argv) < 3:
|
||||
# Default flow example
|
||||
flow = [
|
||||
{"from": "User", "to": "API Gateway", "label": "request"},
|
||||
{"from": "API Gateway", "to": "Service", "label": "route"},
|
||||
{"from": "Service", "to": "Database", "label": "query"}
|
||||
]
|
||||
else:
|
||||
flow = json.loads(sys.argv[2])
|
||||
|
||||
print(generate_flow_diagram(flow))
|
||||
|
||||
elif diagram_type == "c4":
|
||||
if len(sys.argv) < 3:
|
||||
# Default C4 example
|
||||
system = "My Application"
|
||||
actors = ["End User", "Administrator"]
|
||||
external_systems = ["Payment Gateway", "Email Service"]
|
||||
else:
|
||||
config = json.loads(sys.argv[2])
|
||||
system = config.get('system', 'System')
|
||||
actors = config.get('actors', [])
|
||||
external_systems = config.get('external_systems', [])
|
||||
|
||||
print(generate_c4_context_diagram(system, actors, external_systems))
|
||||
|
||||
else:
|
||||
print(f"Unknown diagram type: {diagram_type}")
|
||||
print("Available types: simple, layered, flow, c4")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
345
skills/scripts/generate_mermaid.py
Normal file
345
skills/scripts/generate_mermaid.py
Normal file
@@ -0,0 +1,345 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate Mermaid.js diagrams for architecture documentation.
|
||||
Creates 5 essential diagrams: C4 Context, Container, Component, Data Flow, and Deployment.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from typing import Dict, List, Any
|
||||
|
||||
|
||||
def generate_c4_context(system_info: Dict[str, Any]) -> str:
|
||||
"""Generate C4 Level 1: System Context diagram."""
|
||||
system_name = system_info.get('system_name', 'System')
|
||||
users = system_info.get('users', [])
|
||||
external_systems = system_info.get('external_systems', [])
|
||||
|
||||
diagram = f"""```mermaid
|
||||
C4Context
|
||||
title System Context - {system_name}
|
||||
"""
|
||||
|
||||
# Add users
|
||||
for i, user in enumerate(users):
|
||||
user_id = user.get('id', f'user{i}')
|
||||
user_name = user.get('name', f'User {i}')
|
||||
user_desc = user.get('description', 'System user')
|
||||
diagram += f' Person({user_id}, "{user_name}", "{user_desc}")\n'
|
||||
|
||||
# Add main system
|
||||
system_desc = system_info.get('description', 'Core system functionality')
|
||||
diagram += f' System(system, "{system_name}", "{system_desc}")\n'
|
||||
|
||||
# Add external systems
|
||||
for i, ext_sys in enumerate(external_systems):
|
||||
ext_id = ext_sys.get('id', f'ext{i}')
|
||||
ext_name = ext_sys.get('name', f'External System {i}')
|
||||
ext_desc = ext_sys.get('description', 'Third-party service')
|
||||
diagram += f' System_Ext({ext_id}, "{ext_name}", "{ext_desc}")\n'
|
||||
|
||||
diagram += '\n'
|
||||
|
||||
# Add relationships
|
||||
for user in users:
|
||||
user_id = user.get('id', f'user{len(users)}')
|
||||
rel = user.get('relationship', 'uses')
|
||||
diagram += f' Rel({user_id}, system, "{rel}")\n'
|
||||
|
||||
for ext_sys in external_systems:
|
||||
ext_id = ext_sys.get('id', f'ext{len(external_systems)}')
|
||||
rel = ext_sys.get('relationship', 'integrates with')
|
||||
diagram += f' Rel(system, {ext_id}, "{rel}")\n'
|
||||
|
||||
diagram += '```'
|
||||
return diagram
|
||||
|
||||
|
||||
def generate_c4_container(system_info: Dict[str, Any]) -> str:
|
||||
"""Generate C4 Level 2: Container diagram."""
|
||||
system_name = system_info.get('system_name', 'System')
|
||||
containers = system_info.get('containers', [])
|
||||
external_systems = system_info.get('external_systems', [])
|
||||
|
||||
diagram = f"""```mermaid
|
||||
C4Container
|
||||
title Container Diagram - {system_name}
|
||||
|
||||
Person(user, "User", "System user")
|
||||
|
||||
System_Boundary(system, "{system_name}") {{
|
||||
"""
|
||||
|
||||
# Add containers
|
||||
for container in containers:
|
||||
cont_id = container.get('id', 'container')
|
||||
cont_name = container.get('name', 'Container')
|
||||
cont_tech = container.get('technology', 'Technology')
|
||||
cont_desc = container.get('description', 'Container description')
|
||||
diagram += f' Container({cont_id}, "{cont_name}", "{cont_tech}", "{cont_desc}")\n'
|
||||
|
||||
diagram += ' }\n\n'
|
||||
|
||||
# Add external systems (simplified)
|
||||
for ext_sys in external_systems[:2]: # Limit to 2 for clarity
|
||||
ext_id = ext_sys.get('id', 'ext')
|
||||
ext_name = ext_sys.get('name', 'External System')
|
||||
ext_desc = ext_sys.get('description', 'External service')
|
||||
diagram += f' System_Ext({ext_id}, "{ext_name}", "{ext_desc}")\n'
|
||||
|
||||
diagram += '\n'
|
||||
|
||||
# Add relationships
|
||||
relationships = system_info.get('container_relationships', [])
|
||||
for rel in relationships:
|
||||
from_id = rel.get('from', '')
|
||||
to_id = rel.get('to', '')
|
||||
desc = rel.get('description', 'interacts')
|
||||
protocol = rel.get('protocol', '')
|
||||
if protocol:
|
||||
diagram += f' Rel({from_id}, {to_id}, "{desc} [{protocol}]")\n'
|
||||
else:
|
||||
diagram += f' Rel({from_id}, {to_id}, "{desc}")\n'
|
||||
|
||||
diagram += '```'
|
||||
return diagram
|
||||
|
||||
|
||||
def generate_c4_component(system_info: Dict[str, Any]) -> str:
|
||||
"""Generate C4 Level 3: Component diagram for main container."""
|
||||
container_name = system_info.get('main_container_name', 'API Service')
|
||||
components = system_info.get('components', [])
|
||||
|
||||
diagram = f"""```mermaid
|
||||
C4Component
|
||||
title Component Diagram - {container_name}
|
||||
|
||||
Container_Boundary(container, "{container_name}") {{
|
||||
"""
|
||||
|
||||
# Add components
|
||||
for comp in components:
|
||||
comp_id = comp.get('id', 'component')
|
||||
comp_name = comp.get('name', 'Component')
|
||||
comp_tech = comp.get('technology', 'Technology')
|
||||
comp_desc = comp.get('description', 'Component description')
|
||||
diagram += f' Component({comp_id}, "{comp_name}", "{comp_tech}", "{comp_desc}")\n'
|
||||
|
||||
diagram += ' }\n\n'
|
||||
|
||||
# Add external dependencies
|
||||
ext_deps = system_info.get('component_dependencies', [])
|
||||
for dep in ext_deps:
|
||||
dep_id = dep.get('id', 'dep')
|
||||
dep_name = dep.get('name', 'Dependency')
|
||||
dep_type = dep.get('type', 'Database')
|
||||
dep_tech = dep.get('technology', 'Technology')
|
||||
|
||||
if 'db' in dep_type.lower() or 'database' in dep_type.lower():
|
||||
diagram += f' ContainerDb({dep_id}, "{dep_name}", "{dep_tech}", "{dep_type}")\n'
|
||||
else:
|
||||
diagram += f' System_Ext({dep_id}, "{dep_name}", "{dep_type}")\n'
|
||||
|
||||
diagram += '\n'
|
||||
|
||||
# Add component relationships
|
||||
comp_rels = system_info.get('component_relationships', [])
|
||||
for rel in comp_rels:
|
||||
from_id = rel.get('from', '')
|
||||
to_id = rel.get('to', '')
|
||||
desc = rel.get('description', 'uses')
|
||||
diagram += f' Rel({from_id}, {to_id}, "{desc}")\n'
|
||||
|
||||
diagram += '```'
|
||||
return diagram
|
||||
|
||||
|
||||
def generate_data_flow(system_info: Dict[str, Any]) -> str:
|
||||
"""Generate Data Flow Diagram."""
|
||||
diagram = """```mermaid
|
||||
flowchart LR
|
||||
subgraph sources["📥 Data Sources"]
|
||||
"""
|
||||
|
||||
# Data sources
|
||||
data_sources = system_info.get('data_sources', [])
|
||||
for source in data_sources:
|
||||
source_id = source.get('id', 'source')
|
||||
source_name = source.get('name', 'Source')
|
||||
diagram += f' {source_id}["{source_name}"]\n'
|
||||
|
||||
diagram += ' end\n\n'
|
||||
diagram += ' subgraph processes["⚙️ Data Processing"]\n'
|
||||
|
||||
# Processing steps
|
||||
processes = system_info.get('data_processes', [])
|
||||
for process in processes:
|
||||
proc_id = process.get('id', 'process')
|
||||
proc_name = process.get('name', 'Process')
|
||||
diagram += f' {proc_id}["{proc_name}"]\n'
|
||||
|
||||
diagram += ' end\n\n'
|
||||
diagram += ' subgraph storage["💾 Data Storage"]\n'
|
||||
|
||||
# Storage
|
||||
storages = system_info.get('data_storage', [])
|
||||
for store in storages:
|
||||
store_id = store.get('id', 'store')
|
||||
store_name = store.get('name', 'Storage')
|
||||
store_tech = store.get('technology', '')
|
||||
if store_tech:
|
||||
diagram += f' {store_id}["{store_name}<br/>({store_tech})"]\n'
|
||||
else:
|
||||
diagram += f' {store_id}["{store_name}"]\n'
|
||||
|
||||
diagram += ' end\n\n'
|
||||
diagram += ' subgraph outputs["📤 Data Outputs"]\n'
|
||||
|
||||
# Outputs
|
||||
outputs = system_info.get('data_outputs', [])
|
||||
for output in outputs:
|
||||
out_id = output.get('id', 'output')
|
||||
out_name = output.get('name', 'Output')
|
||||
diagram += f' {out_id}["{out_name}"]\n'
|
||||
|
||||
diagram += ' end\n\n'
|
||||
|
||||
# Add flows
|
||||
flows = system_info.get('data_flows', [])
|
||||
for flow in flows:
|
||||
from_id = flow.get('from', '')
|
||||
to_id = flow.get('to', '')
|
||||
label = flow.get('label', 'data')
|
||||
diagram += f' {from_id} -->|"{label}"| {to_id}\n'
|
||||
|
||||
diagram += '```'
|
||||
return diagram
|
||||
|
||||
|
||||
def generate_deployment(system_info: Dict[str, Any]) -> str:
|
||||
"""Generate C4 Deployment diagram."""
|
||||
system_name = system_info.get('system_name', 'System')
|
||||
cloud_provider = system_info.get('cloud_provider', 'Cloud Provider')
|
||||
|
||||
diagram = f"""```mermaid
|
||||
C4Deployment
|
||||
title Deployment Diagram - {system_name}
|
||||
|
||||
"""
|
||||
|
||||
# Generate deployment nodes
|
||||
deployment_nodes = system_info.get('deployment_nodes', [])
|
||||
|
||||
for node in deployment_nodes:
|
||||
node_id = node.get('id', 'node')
|
||||
node_name = node.get('name', 'Node')
|
||||
node_tech = node.get('technology', 'Technology')
|
||||
containers = node.get('containers', [])
|
||||
nested_nodes = node.get('nested_nodes', [])
|
||||
|
||||
diagram += f' Deployment_Node({node_id}, "{node_name}", "{node_tech}") {{\n'
|
||||
|
||||
# Add nested nodes if any
|
||||
for nested in nested_nodes:
|
||||
nested_id = nested.get('id', 'nested')
|
||||
nested_name = nested.get('name', 'Node')
|
||||
nested_tech = nested.get('technology', 'Tech')
|
||||
nested_containers = nested.get('containers', [])
|
||||
|
||||
diagram += f' Deployment_Node({nested_id}, "{nested_name}", "{nested_tech}") {{\n'
|
||||
|
||||
for cont in nested_containers:
|
||||
cont_id = cont.get('id', 'cont')
|
||||
cont_name = cont.get('name', 'Container')
|
||||
cont_tech = cont.get('technology', 'Tech')
|
||||
cont_desc = cont.get('description', 'Description')
|
||||
|
||||
if 'db' in cont_name.lower() or 'database' in cont_name.lower():
|
||||
diagram += f' ContainerDb({cont_id}, "{cont_name}", "{cont_tech}", "{cont_desc}")\n'
|
||||
else:
|
||||
diagram += f' Container({cont_id}, "{cont_name}", "{cont_tech}", "{cont_desc}")\n'
|
||||
|
||||
diagram += ' }\n'
|
||||
|
||||
# Add direct containers
|
||||
for cont in containers:
|
||||
cont_id = cont.get('id', 'cont')
|
||||
cont_name = cont.get('name', 'Container')
|
||||
cont_tech = cont.get('technology', 'Tech')
|
||||
cont_desc = cont.get('description', 'Description')
|
||||
|
||||
if 'db' in cont_name.lower() or 'database' in cont_name.lower():
|
||||
diagram += f' ContainerDb({cont_id}, "{cont_name}", "{cont_tech}", "{cont_desc}")\n'
|
||||
else:
|
||||
diagram += f' Container({cont_id}, "{cont_name}", "{cont_tech}", "{cont_desc}")\n'
|
||||
|
||||
diagram += ' }\n\n'
|
||||
|
||||
# Add relationships
|
||||
deployment_rels = system_info.get('deployment_relationships', [])
|
||||
for rel in deployment_rels:
|
||||
from_id = rel.get('from', '')
|
||||
to_id = rel.get('to', '')
|
||||
desc = rel.get('description', 'connects')
|
||||
protocol = rel.get('protocol', '')
|
||||
|
||||
if protocol:
|
||||
diagram += f' Rel({from_id}, {to_id}, "{desc}", "{protocol}")\n'
|
||||
else:
|
||||
diagram += f' Rel({from_id}, {to_id}, "{desc}")\n'
|
||||
|
||||
diagram += '```'
|
||||
return diagram
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python generate_mermaid.py <config_json>")
|
||||
print("\nConfig JSON should contain system architecture information")
|
||||
sys.exit(1)
|
||||
|
||||
# Load configuration
|
||||
try:
|
||||
config = json.loads(sys.argv[1])
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error parsing JSON: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Generate all 5 diagrams
|
||||
diagrams = {
|
||||
'c4_context': generate_c4_context(config),
|
||||
'c4_container': generate_c4_container(config),
|
||||
'c4_component': generate_c4_component(config),
|
||||
'data_flow': generate_data_flow(config),
|
||||
'deployment': generate_deployment(config)
|
||||
}
|
||||
|
||||
# Output diagrams
|
||||
print("### Diagram 1: System Context (C4 Level 1)\n")
|
||||
print("**Description**: Shows the system in context with external users and systems\n")
|
||||
print(diagrams['c4_context'])
|
||||
print("\n---\n")
|
||||
|
||||
print("### Diagram 2: Container Diagram (C4 Level 2)\n")
|
||||
print("**Description**: Shows the main technical components and their relationships\n")
|
||||
print(diagrams['c4_container'])
|
||||
print("\n---\n")
|
||||
|
||||
print("### Diagram 3: Component Diagram (C4 Level 3)\n")
|
||||
print("**Description**: Shows internal components of the main container\n")
|
||||
print(diagrams['c4_component'])
|
||||
print("\n---\n")
|
||||
|
||||
print("### Diagram 4: Data Flow Diagram\n")
|
||||
print("**Description**: Shows how data moves through the system\n")
|
||||
print(diagrams['data_flow'])
|
||||
print("\n---\n")
|
||||
|
||||
print("### Diagram 5: Deployment Diagram\n")
|
||||
print("**Description**: Shows infrastructure and deployment topology\n")
|
||||
print(diagrams['deployment'])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
423
skills/scripts/generate_openapi.py
Normal file
423
skills/scripts/generate_openapi.py
Normal file
@@ -0,0 +1,423 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate OpenAPI 3.0 specification from architecture information.
|
||||
Creates a comprehensive API specification for the designed endpoints.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from typing import Dict, List, Any
|
||||
|
||||
|
||||
def generate_openapi_spec(api_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Generate OpenAPI 3.0 specification."""
|
||||
|
||||
system_name = api_info.get('system_name', 'API')
|
||||
version = api_info.get('version', '1.0.0')
|
||||
description = api_info.get('description', 'API for ' + system_name)
|
||||
base_url = api_info.get('base_url', 'https://api.example.com')
|
||||
|
||||
spec = {
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": f"{system_name} API",
|
||||
"description": description,
|
||||
"version": version,
|
||||
"contact": {
|
||||
"name": api_info.get('contact_name', 'API Team'),
|
||||
"email": api_info.get('contact_email', 'api@example.com')
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": base_url,
|
||||
"description": api_info.get('server_description', 'Production server')
|
||||
}
|
||||
],
|
||||
"paths": {},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"securitySchemes": {},
|
||||
"responses": {}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
|
||||
# Add security schemes
|
||||
auth_type = api_info.get('authentication', 'bearer')
|
||||
if auth_type == 'bearer' or auth_type == 'jwt':
|
||||
spec['components']['securitySchemes']['bearerAuth'] = {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
}
|
||||
spec['security'] = [{"bearerAuth": []}]
|
||||
elif auth_type == 'apikey':
|
||||
spec['components']['securitySchemes']['apiKey'] = {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-API-Key"
|
||||
}
|
||||
spec['security'] = [{"apiKey": []}]
|
||||
|
||||
# Add common schemas
|
||||
spec['components']['schemas']['Error'] = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string",
|
||||
"description": "Error message"
|
||||
},
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "Error code"
|
||||
}
|
||||
},
|
||||
"required": ["error"]
|
||||
}
|
||||
|
||||
# Add common responses
|
||||
spec['components']['responses']['UnauthorizedError'] = {
|
||||
"description": "Authentication information is missing or invalid",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spec['components']['responses']['NotFoundError'] = {
|
||||
"description": "The specified resource was not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add tags
|
||||
tags = api_info.get('tags', [])
|
||||
for tag in tags:
|
||||
spec['tags'].append({
|
||||
"name": tag.get('name', 'default'),
|
||||
"description": tag.get('description', '')
|
||||
})
|
||||
|
||||
# Add schemas
|
||||
schemas = api_info.get('schemas', {})
|
||||
for schema_name, schema_def in schemas.items():
|
||||
spec['components']['schemas'][schema_name] = schema_def
|
||||
|
||||
# Add endpoints
|
||||
endpoints = api_info.get('endpoints', [])
|
||||
for endpoint in endpoints:
|
||||
path = endpoint.get('path', '/')
|
||||
method = endpoint.get('method', 'get').lower()
|
||||
|
||||
if path not in spec['paths']:
|
||||
spec['paths'][path] = {}
|
||||
|
||||
operation = {
|
||||
"summary": endpoint.get('summary', ''),
|
||||
"description": endpoint.get('description', ''),
|
||||
"operationId": endpoint.get('operation_id', method + path.replace('/', '_')),
|
||||
"tags": endpoint.get('tags', ['default']),
|
||||
"responses": {}
|
||||
}
|
||||
|
||||
# Add parameters
|
||||
parameters = endpoint.get('parameters', [])
|
||||
if parameters:
|
||||
operation['parameters'] = parameters
|
||||
|
||||
# Add request body
|
||||
request_body = endpoint.get('request_body', None)
|
||||
if request_body:
|
||||
operation['requestBody'] = request_body
|
||||
|
||||
# Add responses
|
||||
responses = endpoint.get('responses', {})
|
||||
if responses:
|
||||
operation['responses'] = responses
|
||||
else:
|
||||
# Default responses
|
||||
operation['responses'] = {
|
||||
"200": {
|
||||
"description": "Successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/UnauthorizedError"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/NotFoundError"
|
||||
}
|
||||
}
|
||||
|
||||
spec['paths'][path][method] = operation
|
||||
|
||||
return spec
|
||||
|
||||
|
||||
def generate_default_crud_api(resource_name: str) -> Dict[str, Any]:
|
||||
"""Generate a default CRUD API specification for a resource."""
|
||||
|
||||
resource_lower = resource_name.lower()
|
||||
resource_title = resource_name.title()
|
||||
|
||||
api_info = {
|
||||
"system_name": f"{resource_title} Management",
|
||||
"version": "1.0.0",
|
||||
"description": f"API for managing {resource_lower} resources",
|
||||
"base_url": "https://api.example.com/v1",
|
||||
"authentication": "bearer",
|
||||
"tags": [
|
||||
{
|
||||
"name": resource_lower,
|
||||
"description": f"{resource_title} operations"
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
resource_title: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": f"{resource_title} ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": f"{resource_title} name"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Creation timestamp"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Last update timestamp"
|
||||
}
|
||||
},
|
||||
"required": ["id", "name"]
|
||||
},
|
||||
f"{resource_title}Input": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": f"{resource_title} name"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"endpoints": [
|
||||
{
|
||||
"path": f"/{resource_lower}s",
|
||||
"method": "get",
|
||||
"summary": f"List all {resource_lower}s",
|
||||
"description": f"Retrieve a list of {resource_lower}s with pagination",
|
||||
"operation_id": f"list{resource_title}s",
|
||||
"tags": [resource_lower],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"description": "Page number",
|
||||
"schema": {"type": "integer", "default": 1}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Items per page",
|
||||
"schema": {"type": "integer", "default": 20}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": f"#/components/schemas/{resource_title}"
|
||||
}
|
||||
},
|
||||
"total": {"type": "integer"},
|
||||
"page": {"type": "integer"},
|
||||
"limit": {"type": "integer"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": f"/{resource_lower}s",
|
||||
"method": "post",
|
||||
"summary": f"Create a new {resource_lower}",
|
||||
"description": f"Create a new {resource_lower} resource",
|
||||
"operation_id": f"create{resource_title}",
|
||||
"tags": [resource_lower],
|
||||
"request_body": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": f"#/components/schemas/{resource_title}Input"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": f"#/components/schemas/{resource_title}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": f"/{resource_lower}s/{{id}}",
|
||||
"method": "get",
|
||||
"summary": f"Get a {resource_lower} by ID",
|
||||
"description": f"Retrieve a specific {resource_lower} by its ID",
|
||||
"operation_id": f"get{resource_title}",
|
||||
"tags": [resource_lower],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"description": f"{resource_title} ID",
|
||||
"schema": {"type": "string", "format": "uuid"}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": f"#/components/schemas/{resource_title}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": f"/{resource_lower}s/{{id}}",
|
||||
"method": "put",
|
||||
"summary": f"Update a {resource_lower}",
|
||||
"description": f"Update an existing {resource_lower} resource",
|
||||
"operation_id": f"update{resource_title}",
|
||||
"tags": [resource_lower],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"description": f"{resource_title} ID",
|
||||
"schema": {"type": "string", "format": "uuid"}
|
||||
}
|
||||
],
|
||||
"request_body": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": f"#/components/schemas/{resource_title}Input"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Updated successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": f"#/components/schemas/{resource_title}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": f"/{resource_lower}s/{{id}}",
|
||||
"method": "delete",
|
||||
"summary": f"Delete a {resource_lower}",
|
||||
"description": f"Delete a {resource_lower} resource",
|
||||
"operation_id": f"delete{resource_title}",
|
||||
"tags": [resource_lower],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"description": f"{resource_title} ID",
|
||||
"schema": {"type": "string", "format": "uuid"}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Deleted successfully"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return generate_openapi_spec(api_info)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python generate_openapi.py <config_json|resource_name>")
|
||||
print("\nExamples:")
|
||||
print(" python generate_openapi.py 'User' # Generate default CRUD API")
|
||||
print(" python generate_openapi.py '{...}' # Generate from JSON config")
|
||||
sys.exit(1)
|
||||
|
||||
input_data = sys.argv[1]
|
||||
|
||||
# Try to parse as JSON first
|
||||
try:
|
||||
config = json.loads(input_data)
|
||||
spec = generate_openapi_spec(config)
|
||||
except json.JSONDecodeError:
|
||||
# Treat as resource name for default CRUD API
|
||||
spec = generate_default_crud_api(input_data)
|
||||
|
||||
# Output OpenAPI spec
|
||||
print(json.dumps(spec, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
126
skills/scripts/validate_architecture.py
Normal file
126
skills/scripts/validate_architecture.py
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate ARCHITECTURE.md for completeness and consistency.
|
||||
Checks that all required sections are present and have content.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Required sections in ARCHITECTURE.md
|
||||
REQUIRED_SECTIONS = [
|
||||
"1. Project Structure",
|
||||
"2. High-Level System Diagram",
|
||||
"3. Core Components",
|
||||
"4. Data Stores",
|
||||
"5. External Integrations / APIs",
|
||||
"6. Deployment & Infrastructure",
|
||||
"7. Security Considerations",
|
||||
"8. Development & Testing Environment",
|
||||
"9. Future Considerations / Roadmap",
|
||||
"10. Project Identification",
|
||||
"11. Glossary / Acronyms"
|
||||
]
|
||||
|
||||
|
||||
def validate_architecture(file_path):
|
||||
"""Validate ARCHITECTURE.md file."""
|
||||
|
||||
if not Path(file_path).exists():
|
||||
return False, [f"File not found: {file_path}"]
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
issues = []
|
||||
warnings = []
|
||||
|
||||
# Check for required sections
|
||||
missing_sections = []
|
||||
for section in REQUIRED_SECTIONS:
|
||||
# Create regex pattern that matches section headings
|
||||
pattern = re.compile(rf'^##\s+{re.escape(section)}', re.MULTILINE)
|
||||
if not pattern.search(content):
|
||||
missing_sections.append(section)
|
||||
|
||||
if missing_sections:
|
||||
issues.append(f"Missing required sections: {', '.join(missing_sections)}")
|
||||
|
||||
# Check if sections have content (not just placeholders)
|
||||
empty_sections = []
|
||||
lines = content.split('\n')
|
||||
current_section = None
|
||||
section_content = []
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('## '):
|
||||
# Save previous section
|
||||
if current_section and len(section_content) < 3:
|
||||
# Section has fewer than 3 lines of content
|
||||
if not any('[' in l or 'TODO' in l.upper() for l in section_content):
|
||||
empty_sections.append(current_section)
|
||||
# Start new section
|
||||
current_section = line.strip('# ').strip()
|
||||
section_content = []
|
||||
elif line.strip() and not line.startswith('#'):
|
||||
section_content.append(line)
|
||||
|
||||
# Check last section
|
||||
if current_section and len(section_content) < 3:
|
||||
if not any('[' in l or 'TODO' in l.upper() for l in section_content):
|
||||
empty_sections.append(current_section)
|
||||
|
||||
if empty_sections:
|
||||
warnings.append(f"Sections with minimal content: {', '.join(empty_sections[:3])}")
|
||||
|
||||
# Check for placeholder text
|
||||
placeholders = ['[TODO]', '[FILL IN]', '[INSERT', '[e.g.,']
|
||||
placeholder_count = sum(content.count(p) for p in placeholders)
|
||||
if placeholder_count > 10:
|
||||
warnings.append(f"Found {placeholder_count} placeholders - consider filling them in")
|
||||
|
||||
# Check for Project Identification fields
|
||||
required_fields = ['Project Name:', 'Repository URL:', 'Primary Contact', 'Date of Last Update:']
|
||||
missing_fields = []
|
||||
for field in required_fields:
|
||||
if field not in content:
|
||||
missing_fields.append(field)
|
||||
|
||||
if missing_fields:
|
||||
warnings.append(f"Missing project identification fields: {', '.join(missing_fields)}")
|
||||
|
||||
# Report results
|
||||
if issues:
|
||||
return False, issues
|
||||
|
||||
return True, warnings
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python validate_architecture.py <path-to-ARCHITECTURE.md>")
|
||||
sys.exit(1)
|
||||
|
||||
file_path = sys.argv[1]
|
||||
valid, messages = validate_architecture(file_path)
|
||||
|
||||
if valid:
|
||||
print("✅ ARCHITECTURE.md validation PASSED")
|
||||
if messages:
|
||||
print("\n⚠️ Warnings:")
|
||||
for msg in messages:
|
||||
print(f" - {msg}")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("❌ ARCHITECTURE.md validation FAILED")
|
||||
print("\n❌ Issues:")
|
||||
for msg in messages:
|
||||
print(f" - {msg}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user