Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:05:07 +08:00
commit 160c5a21b3
20 changed files with 4953 additions and 0 deletions

View 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()

View 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()

View 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()

View 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()

View 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()