🔀

AI Technical Writer

Medium6 tools

Automated documentation generation from code, APIs, and technical specifications.

PydanticAIClaudeGitHubSwagger/OpenAPIMkDocsNotion

Workflow Steps

  1. 1

    Analyzer Agent examines codebase structure

  2. 2

    Extractor Agent pulls API specs and comments

  3. 3

    Writer Agent generates documentation sections

  4. 4

    Reviewer Agent ensures accuracy and completeness

  5. 5

    Formatter Agent creates final documentation

  6. 6

    Publisher Agent deploys to documentation site

Download

Documentation

AI Technical Writer

Overview

This workflow automates technical documentation generation from codebases, API specifications, and technical documents. It produces comprehensive, accurate documentation including API references, architecture docs, and user guides.

Difficulty

Medium - Requires codebase access and understanding of documentation standards.

Tools Required

  • PydanticAI / Claude: Code analysis and documentation generation
  • GitHub: Codebase access and version control
  • Swagger/OpenAPI: API specification parsing
  • MkDocs / Docusaurus: Documentation site generation
  • Notion: Documentation storage and collaboration

Workflow Steps

Step 1: Analyzer Agent

Examines codebase structure and patterns.

import os
import ast
from pathlib import Path
from typing import Dict, List, Set

class CodeAnalyzer:
    """
    Analyze codebase structure and extract documentation-relevant information.
    """
    
    def __init__(self, repo_path: str):
        self.repo_path = Path(repo_path)
        self.structure = {}
        self.dependencies = {}
        self.api_endpoints = []
    
    def analyze(self) -> Dict:
        """
        Perform comprehensive codebase analysis.
        
        Returns:
            Structured analysis of the codebase
        """
        # Get file structure
        self.structure = self._get_file_structure()
        
        # Analyze imports and dependencies
        self.dependencies = self._analyze_dependencies()
        
        # Extract API endpoints (for web frameworks)
        self.api_endpoints = self._extract_api_endpoints()
        
        # Identify key classes and functions
        self.public_api = self._extract_public_api()
        
        return {
            "structure": self.structure,
            "dependencies": self.dependencies,
            "api_endpoints": self.api_endpoints,
            "public_api": self.public_api,
            "tech_stack": self._identify_tech_stack()
        }
    
    def _get_file_structure(self) -> Dict:
        """Get the file and directory structure."""
        structure = {}
        
        for root, dirs, files in os.walk(self.repo_path):
            # Skip hidden directories and common non-code directories
            dirs[:] = [d for d in dirs if not d.startswith('.') and d not in 
                      ['node_modules', '__pycache__', 'venv', '.git', 'dist', 'build']]
            
            rel_path = os.path.relpath(root, self.repo_path)
            
            if files:
                structure[rel_path] = {
                    "files": files,
                    "count": len(files)
                }
        
        return structure
    
    def _analyze_dependencies(self) -> Dict:
        """Analyze project dependencies."""
        dependencies = {
            "python": [],
            "javascript": [],
            "other": []
        }
        
        # Python: requirements.txt, pyproject.toml, setup.py
        req_files = ['requirements.txt', 'requirements/*.txt']
        for req_file in req_files:
            for path in self.repo_path.glob(req_file):
                if path.exists():
                    dependencies["python"] = self._parse_requirements(path)
        
        # Python: pyproject.toml
        pyproject = self.repo_path / 'pyproject.toml'
        if pyproject.exists():
            dependencies["python"] = self._parse_pyproject(pyproject)
        
        # JavaScript: package.json
        package_json = self.repo_path / 'package.json'
        if package_json.exists():
            dependencies["javascript"] = self._parse_package_json(package_json)
        
        return dependencies
    
    def _extract_api_endpoints(self) -> List[Dict]:
        """Extract API endpoints from web framework code."""
        endpoints = []
        
        # Look for common web framework patterns
        for py_file in self.repo_path.rglob('*.py'):
            content = py_file.read_text()
            
            # FastAPI patterns
            if '@app.' in content or '@router.' in content:
                endpoints.extend(self._parse_fastapi(py_file))
            
            # Flask patterns
            if '@app.route' in content or '@blueprint.route' in content:
                endpoints.extend(self._parse_flask(py_file))
            
            # Django patterns
            if 'path(' in content or 'url(' in content:
                endpoints.extend(self._parse_django(py_file))
        
        return endpoints
    
    def _parse_fastapi(self, file_path: Path) -> List[Dict]:
        """Parse FastAPI routes."""
        endpoints = []
        content = file_path.read_text()
        
        # Simple regex-based extraction (would use AST in production)
        import re
        patterns = [
            r'@(app|router)\.(get|post|put|delete|patch)\([\'"]([^\'"]+)[\'"]',
            r'@(app|router)\.(api_router)\.add_api_route\([\'"]([^\'"]+)[\'"]'
        ]
        
        for pattern in patterns:
            matches = re.findall(pattern, content)
            for match in matches:
                endpoints.append({
                    "method": match[1].upper(),
                    "path": match[2],
                    "file": str(file_path.relative_to(self.repo_path))
                })
        
        return endpoints
    
    def _extract_public_api(self) -> Dict:
        """Extract public classes and functions."""
        public_api = {
            "classes": [],
            "functions": [],
            "modules": []
        }
        
        for py_file in self.repo_path.rglob('*.py'):
            if py_file.name.startswith('_'):
                continue
            
            try:
                tree = ast.parse(py_file.read_text())
                
                for node in ast.walk(tree):
                    if isinstance(node, ast.ClassDef):
                        if not node.name.startswith('_'):
                            public_api["classes"].append({
                                "name": node.name,
                                "file": str(py_file.relative_to(self.repo_path)),
                                "methods": [m.name for m in node.body 
                                           if isinstance(m, ast.FunctionDef) 
                                           and not m.name.startswith('_')]
                            })
                    
                    elif isinstance(node, ast.FunctionDef):
                        if not node.name.startswith('_') and isinstance(node, ast.Module):
                            public_api["functions"].append({
                                "name": node.name,
                                "file": str(py_file.relative_to(self.repo_path)),
                                "args": [arg.arg for arg in node.args.args]
                            })
            except SyntaxError:
                continue
        
        return public_api
    
    def _identify_tech_stack(self) -> Dict:
        """Identify the technology stack."""
        stack = {
            "languages": [],
            "frameworks": [],
            "databases": [],
            "tools": []
        }
        
        # Check for common indicators
        files = [f.name for f in self.repo_path.iterdir()]
        
        if 'package.json' in files:
            stack["languages"].append("JavaScript/TypeScript")
        
        if any(f.endswith('.py') for f in self.repo_path.rglob('*')):
            stack["languages"].append("Python")
        
        # Check for framework indicators
        content = ""
        for py_file in self.repo_path.rglob('*.py'):
            content += py_file.read_text()[:1000]
        
        if 'fastapi' in content.lower():
            stack["frameworks"].append("FastAPI")
        if 'flask' in content.lower():
            stack["frameworks"].append("Flask")
        if 'django' in content.lower():
            stack["frameworks"].append("Django")
        if 'langchain' in content.lower():
            stack["frameworks"].append("LangChain")
        
        if 'sqlalchemy' in content.lower():
            stack["databases"].append("SQLAlchemy/SQL")
        if 'mongodb' in content.lower() or 'pymongo' in content.lower():
            stack["databases"].append("MongoDB")
        
        return stack

# Example usage
analyzer = CodeAnalyzer("/path/to/repo")
analysis = analyzer.analyze()

"""
{
  "structure": {
    "src": {"files": ["__init__.py", "main.py", "api.py"], "count": 3},
    "src/api": {"files": ["routes.py", "schemas.py"], "count": 2}
  },
  "dependencies": {
    "python": ["fastapi", "pydantic", "sqlalchemy"],
    "javascript": []
  },
  "api_endpoints": [
    {"method": "GET", "path": "/api/users", "file": "src/api/routes.py"},
    {"method": "POST", "path": "/api/users", "file": "src/api/routes.py"}
  ],
  "public_api": {
    "classes": [{"name": "User", "file": "src/models.py", "methods": ["create", "get", "update"]}],
    "functions": []
  },
  "tech_stack": {
    "languages": ["Python"],
    "frameworks": ["FastAPI"],
    "databases": ["SQLAlchemy"],
    "tools": []
  }
}
"""

Step 2: Extractor Agent

Pulls API specs and code comments.

import yaml
import json
from openapi_spec_validator import validate

class SpecExtractor:
    """
    Extract API specifications and code documentation.
    """
    
    def extract_openapi(self, spec_path: str) -> Dict:
        """
        Extract and validate OpenAPI specification.
        
        Args:
            spec_path: Path to OpenAPI spec file
            
        Returns:
            Validated OpenAPI specification
        """
        with open(spec_path, 'r') as f:
            if spec_path.endswith('.yaml') or spec_path.endswith('.yml'):
                spec = yaml.safe_load(f)
            else:
                spec = json.load(f)
        
        # Validate spec
        try:
            validate(spec)
        except Exception as e:
            print(f"Warning: OpenAPI spec validation failed: {e}")
        
        return {
            "openapi": spec.get("openapi", "3.0.0"),
            "info": spec.get("info", {}),
            "servers": spec.get("servers", []),
            "paths": self._extract_paths(spec.get("paths", {})),
            "components": spec.get("components", {}),
            "tags": spec.get("tags", [])
        }
    
    def _extract_paths(self, paths: Dict) -> List[Dict]:
        """Extract detailed path information."""
        extracted = []
        
        for path, methods in paths.items():
            for method, operation in methods.items():
                if method in ['get', 'post', 'put', 'delete', 'patch', 'options', 'head']:
                    extracted.append({
                        "path": path,
                        "method": method.upper(),
                        "summary": operation.get("summary", ""),
                        "description": operation.get("description", ""),
                        "operation_id": operation.get("operationId"),
                        "tags": operation.get("tags", []),
                        "parameters": self._extract_parameters(operation.get("parameters", [])),
                        "request_body": operation.get("requestBody"),
                        "responses": self._extract_responses(operation.get("responses", {})),
                        "security": operation.get("security", [])
                    })
        
        return extracted
    
    def _extract_parameters(self, parameters: List[Dict]) -> List[Dict]:
        """Extract parameter details."""
        extracted = []
        
        for param in parameters:
            extracted.append({
                "name": param.get("name"),
                "in": param.get("in"),
                "required": param.get("required", False),
                "description": param.get("description", ""),
                "schema": param.get("schema", {}),
                "example": param.get("example")
            })
        
        return extracted
    
    def _extract_responses(self, responses: Dict) -> Dict:
        """Extract response details."""
        extracted = {}
        
        for status_code, response in responses.items():
            extracted[status_code] = {
                "description": response.get("description", ""),
                "content": response.get("content", {}),
                "headers": response.get("headers", {})
            }
        
        return extracted
    
    def extract_docstrings(self, code_analysis: Dict) -> List[Dict]:
        """
        Extract docstrings from codebase.
        
        Args:
            code_analysis: Output from CodeAnalyzer
            
        Returns:
            List of documented classes and functions
        """
        docstrings = []
        
        for cls in code_analysis["public_api"]["classes"]:
            docstrings.append({
                "type": "class",
                "name": cls["name"],
                "file": cls["file"],
                "methods": cls["methods"]
            })
        
        for func in code_analysis["public_api"]["functions"]:
            docstrings.append({
                "type": "function",
                "name": func["name"],
                "file": func["file"],
                "args": func["args"]
            })
        
        return docstrings

# Example usage
extractor = SpecExtractor()
api_spec = extractor.extract_openapi("docs/openapi.yaml")
docstrings = extractor.extract_docstrings(analysis)

Step 3: Writer Agent

Generates documentation sections.

class DocumentationWriter:
    """
    Generate documentation from code analysis and API specs.
    """
    
    def generate_documentation(self, analysis: Dict, api_spec: Dict, 
                               docstrings: List[Dict], config: Dict) -> Dict:
        """
        Generate comprehensive documentation.
        
        Args:
            analysis: Codebase analysis
            api_spec: API specification
            docstrings: Extracted docstrings
            config: Documentation configuration
            
        Returns:
            Generated documentation sections
        """
        docs = {
            "overview": self._generate_overview(analysis, config),
            "installation": self._generate_installation(analysis),
            "quickstart": self._generate_quickstart(analysis, config),
            "api_reference": self._generate_api_reference(api_spec),
            "architecture": self._generate_architecture(analysis),
            "examples": self._generate_examples(analysis, config),
            "contributing": self._generate_contributing(config)
        }
        
        return docs
    
    def _generate_overview(self, analysis: Dict, config: Dict) -> str:
        """Generate project overview."""
        prompt = f"""
        Write a comprehensive project overview for documentation.
        
        Project Information:
        - Tech Stack: {analysis['tech_stack']}
        - Structure: {len(analysis['structure'])} directories, {sum(f['count'] for f in analysis['structure'].values())} files
        - API Endpoints: {len(analysis['api_endpoints'])}
        - Public Classes: {len(analysis['public_api']['classes'])}
        - Public Functions: {len(analysis['public_api']['functions'])}
        
        Project Name: {config.get('project_name', 'This Project')}
        Description: {config.get('description', 'A comprehensive documentation project')}
        
        Write a 3-4 paragraph overview covering:
        1. What the project does
        2. Key features and capabilities
        3. Target audience and use cases
        4. Architecture highlights
        
        Tone: Professional, clear, accessible to developers
        """
        
        return call_claude(prompt)
    
    def _generate_installation(self, analysis: Dict) -> str:
        """Generate installation instructions."""
        deps = analysis.get("dependencies", {})
        
        installation = "## Installation\n\n"
        
        if deps.get("python"):
            installation += "### Python\n\n"
            installation += "```bash\n"
            installation += "pip install " + " ".join(deps["python"]) + "\n"
            installation += "```\n\n"
        
        if deps.get("javascript"):
            installation += "### JavaScript/TypeScript\n\n"
            installation += "```bash\n"
            installation += "npm install " + " ".join(deps["javascript"]) + "\n"
            installation += "```\n\n"
        
        installation += "### From Source\n\n"
        installation += "```bash\n"
        installation += "git clone https://github.com/user/repo.git\n"
        installation += "cd repo\n"
        installation += "pip install -e .\n"
        installation += "```\n"
        
        return installation
    
    def _generate_quickstart(self, analysis: Dict, config: Dict) -> str:
        """Generate quickstart guide."""
        prompt = f"""
        Write a quickstart guide for {config.get('project_name', 'this project')}.
        
        Key Features:
        - {len(analysis['api_endpoints'])} API endpoints
        - {len(analysis['public_api']['classes'])} main classes
        
        Include:
        1. Minimal working example (5-10 lines of code)
        2. Explanation of what the code does
        3. Expected output
        
        Keep it simple and actionable.
        """
        
        return call_claude(prompt)
    
    def _generate_api_reference(self, api_spec: Dict) -> str:
        """Generate API reference from OpenAPI spec."""
        api_ref = "# API Reference\n\n"
        
        # Group by tags
        tags = api_spec.get("tags", [])
        paths = api_spec.get("paths", [])
        
        for tag in tags:
            api_ref += f"## {tag['name']}\n\n"
            if tag.get("description"):
                api_ref += f"{tag['description']}\n\n"
            
            # Find endpoints for this tag
            tag_endpoints = [p for p in paths if tag['name'] in p.get('tags', [])]
            
            for endpoint in tag_endpoints:
                api_ref += f"### {endpoint['method']} {endpoint['path']}\n\n"
                
                if endpoint.get('summary'):
                    api_ref += f"**{endpoint['summary']}**\n\n"
                
                if endpoint.get('description'):
                    api_ref += f"{endpoint['description']}\n\n"
                
                # Parameters
                if endpoint.get('parameters'):
                    api_ref += "**Parameters:**\n\n"
                    api_ref += "| Name | In | Required | Description |\n"
                    api_ref += "|------|-----|----------|-------------|\n"
                    for param in endpoint['parameters']:
                        api_ref += f"| {param['name']} | {param['in']} | {'Yes' if param['required'] else 'No'} | {param.get('description', '')} |\n"
                    api_ref += "\n"
                
                # Responses
                if endpoint.get('responses'):
                    api_ref += "**Responses:**\n\n"
                    for status, response in endpoint['responses'].items():
                        api_ref += f"- **{status}**: {response.get('description', '')}\n"
                    api_ref += "\n"
        
        return api_ref
    
    def _generate_architecture(self, analysis: Dict) -> str:
        """Generate architecture documentation."""
        prompt = f"""
        Write architecture documentation based on this codebase analysis.
        
        Structure:
        {json.dumps(analysis['structure'], indent=2)}
        
        Tech Stack:
        {json.dumps(analysis['tech_stack'], indent=2)}
        
        Include:
        1. High-level architecture overview
        2. Directory structure explanation
        3. Key components and their responsibilities
        4. Data flow diagram description
        
        Use mermaid diagrams where appropriate.
        """
        
        return call_claude(prompt)
    
    def _generate_examples(self, analysis: Dict, config: Dict) -> str:
        """Generate code examples."""
        prompt = f"""
        Generate practical code examples for {config.get('project_name', 'this project')}.
        
        Public API:
        - Classes: {analysis['public_api']['classes']}
        - Functions: {analysis['public_api']['functions']}
        
        Create 3-5 examples:
        1. Basic usage
        2. Common use case
        3. Advanced feature
        
        Each example should be complete, runnable code with comments.
        """
        
        return call_claude(prompt)

# Example usage
writer = DocumentationWriter()
docs = writer.generate_documentation(analysis, api_spec, docstrings, {
    "project_name": "MyAPI",
    "description": "A modern REST API for managing resources"
})

Step 4: Reviewer Agent

Ensures accuracy and completeness.

class DocReviewer:
    """
    Review and validate generated documentation.
    """
    
    def review(self, docs: Dict, analysis: Dict, api_spec: Dict) -> Dict:
        """
        Review documentation for accuracy and completeness.
        
        Args:
            docs: Generated documentation
            analysis: Codebase analysis
            api_spec: API specification
            
        Returns:
            Review results with suggestions
        """
        review = {
            "accuracy": self._check_accuracy(docs, analysis, api_spec),
            "completeness": self._check_completeness(docs, analysis),
            "consistency": self._check_consistency(docs),
            "clarity": self._check_clarity(docs),
            "suggestions": []
        }
        
        # Generate improvement suggestions
        review["suggestions"] = self._generate_suggestions(review)
        
        return review
    
    def _check_accuracy(self, docs: Dict, analysis: Dict, api_spec: Dict) -> Dict:
        """Check documentation accuracy against source."""
        issues = []
        
        # Verify API endpoints match
        doc_endpoints = self._extract_documented_endpoints(docs.get("api_reference", ""))
        actual_endpoints = api_spec.get("paths", [])
        
        for endpoint in actual_endpoints:
            if endpoint not in doc_endpoints:
                issues.append({
                    "type": "missing_endpoint",
                    "severity": "high",
                    "message": f"Endpoint {endpoint['method']} {endpoint['path']} not documented"
                })
        
        # Verify class/function documentation
        for cls in analysis["public_api"]["classes"]:
            if cls["name"] not in docs.get("api_reference", ""):
                issues.append({
                    "type": "missing_class",
                    "severity": "medium",
                    "message": f"Class {cls['name']} not documented"
                })
        
        return {
            "passed": len([i for i in issues if i["severity"] == "high"]) == 0,
            "issues": issues
        }
    
    def _check_completeness(self, docs: Dict, analysis: Dict) -> Dict:
        """Check documentation completeness."""
        required_sections = ["overview", "installation", "quickstart", "api_reference"]
        missing = [s for s in required_sections if s not in docs or not docs[s]]
        
        return {
            "passed": len(missing) == 0,
            "missing_sections": missing
        }
    
    def _check_consistency(self, docs: Dict) -> Dict:
        """Check documentation consistency."""
        issues = []
        
        # Check for consistent terminology
        terms = self._extract_key_terms(docs)
        for term, variations in terms.items():
            if len(variations) > 1:
                issues.append({
                    "type": "inconsistent_terminology",
                    "severity": "low",
                    "message": f"Term '{term}' used as: {', '.join(variations)}"
                })
        
        return {
            "passed": len([i for i in issues if i["severity"] == "high"]) == 0,
            "issues": issues
        }
    
    def _check_clarity(self, docs: Dict) -> Dict:
        """Check documentation clarity."""
        prompt = f"""
        Review this documentation for clarity and readability.
        
        Documentation:
        {str(docs)[:3000]}
        
        Evaluate:
        1. Is the language clear and accessible?
        2. Are technical terms explained?
        3. Is the structure logical?
        4. Are examples helpful?
        
        Provide specific suggestions for improvement.
        """
        
        feedback = call_claude(prompt)
        
        return {
            "feedback": feedback,
            "readability_score": self._estimate_readability(docs)
        }
    
    def _generate_suggestions(self, review: Dict) -> List[str]:
        """Generate improvement suggestions."""
        suggestions = []
        
        for section, result in review.items():
            if isinstance(result, dict) and result.get("issues"):
                for issue in result["issues"]:
                    suggestions.append(f"[{issue['severity'].upper()}] {issue['message']}")
        
        return suggestions

# Example review
reviewer = DocReviewer()
review = reviewer.review(docs, analysis, api_spec)

"""
{
  "accuracy": {
    "passed": true,
    "issues": []
  },
  "completeness": {
    "passed": true,
    "missing_sections": []
  },
  "consistency": {
    "passed": true,
    "issues": [
      {"type": "inconsistent_terminology", "severity": "low", "message": "Term 'endpoint' used as: API endpoint, endpoint, route"}
    ]
  },
  "clarity": {
    "feedback": "Documentation is clear and well-structured. Consider adding more examples for advanced features.",
    "readability_score": 85
  },
  "suggestions": [
    "[LOW] Term 'endpoint' used as: API endpoint, endpoint, route"
  ]
}
"""

Step 5: Formatter Agent

Creates final documentation.

class DocumentationFormatter:
    """
    Format documentation for different output formats.
    """
    
    def format(self, docs: Dict, review: Dict, config: Dict) -> Dict:
        """
        Format documentation for output.
        
        Args:
            docs: Generated documentation
            review: Review results
            config: Formatting configuration
            
        Returns:
            Formatted documentation files
        """
        outputs = {}
        
        if config.get("format") == "mkdocs":
            outputs = self._format_mkdocs(docs, review, config)
        elif config.get("format") == "docusaurus":
            outputs = self._format_docusaurus(docs, review, config)
        elif config.get("format") == "notion":
            outputs = self._format_notion(docs, review, config)
        elif config.get("format") == "markdown":
            outputs = self._format_markdown(docs, review, config)
        
        return outputs
    
    def _format_mkdocs(self, docs: Dict, review: Dict, config: Dict) -> Dict:
        """Format for MkDocs."""
        outputs = {}
        
        # mkdocs.yml
        outputs["mkdocs.yml"] = f"""
site_name: {config.get('project_name', 'Documentation')}
site_description: {config.get('description', 'Project documentation')}
theme:
  name: material
  palette:
    - media: "(prefers-color-scheme)"
      toggle:
        icon: material/brightness-auto
        name: Switch to light mode
    - media: "(prefers-color-scheme: light)"
      scheme: default
      toggle:
        icon: material/brightness-7
        name: Switch to dark mode
    - media: "(prefers-color-scheme: dark)"
      scheme: slate
      toggle:
        icon: material/brightness-4
        name: Switch to system preference

nav:
  - Home: index.md
  - Getting Started:
    - Installation: installation.md
    - Quickstart: quickstart.md
  - API Reference: api_reference.md
  - Architecture: architecture.md
  - Examples: examples.md
  - Contributing: contributing.md

plugins:
  - search
  - mkdocstrings

markdown_extensions:
  - admonition
  - codehilite
  - pymdownx.highlight
  - pymdownx.superfences
  - pymdownx.tabbed
        """
        
        # index.md
        outputs["docs/index.md"] = f"""# {config.get('project_name', 'Documentation')}

{docs.get('overview', '')}

## Quick Links

- [Installation](installation.md)
- [Quickstart Guide](quickstart.md)
- [API Reference](api_reference.md)
        """
        
        # Other pages
        for section, content in docs.items():
            if section not in ["overview"]:
                outputs[f"docs/{section}.md"] = content
        
        return outputs
    
    def _format_markdown(self, docs: Dict, review: Dict, config: Dict) -> Dict:
        """Format as standalone Markdown files."""
        outputs = {}
        
        outputs["README.md"] = f"""# {config.get('project_name', 'Documentation')}

{docs.get('overview', '')}

## Installation

{docs.get('installation', '')}

## Quickstart

{docs.get('quickstart', '')}

## API Reference

{docs.get('api_reference', '')}

## Architecture

{docs.get('architecture', '')}

## Examples

{docs.get('examples', '')}

## Contributing

{docs.get('contributing', '')}

---

*Generated by AI Technical Writer*
        """
        
        return outputs

# Example formatting
formatter = DocumentationFormatter()
outputs = formatter.format(docs, review, {
    "project_name": "MyAPI",
    "description": "A modern REST API",
    "format": "mkdocs"
})

Step 6: Publisher Agent

Deploys documentation.

class DocumentationPublisher:
    """
    Publish documentation to various platforms.
    """
    
    def publish(self, outputs: Dict, config: Dict) -> Dict:
        """
        Publish documentation.
        
        Args:
            outputs: Formatted documentation files
            config: Publishing configuration
            
        Returns:
            Publishing results
        """
        results = {}
        
        if config.get("deploy_to") == "github_pages":
            results = self._deploy_github_pages(outputs, config)
        elif config.get("deploy_to") == "netlify":
            results = self._deploy_netlify(outputs, config)
        elif config.get("deploy_to") == "notion":
            results = self._deploy_notion(outputs, config)
        
        return results
    
    def _deploy_github_pages(self, outputs: Dict, config: Dict) -> Dict:
        """Deploy to GitHub Pages."""
        import subprocess
        
        # Clone docs repo or use existing
        repo_url = config.get("repo_url")
        
        # Create gh-pages branch if needed
        subprocess.run(["git", "checkout", "gh-pages"], capture_output=True)
        
        # Write files
        for path, content in outputs.items():
            file_path = Path(path)
            file_path.parent.mkdir(parents=True, exist_ok=True)
            file_path.write_text(content)
        
        # Commit and push
        subprocess.run(["git", "add", "."], capture_output=True)
        subprocess.run(["git", "commit", "-m", "Update documentation"], capture_output=True)
        subprocess.run(["git", "push", "origin", "gh-pages"], capture_output=True)
        
        return {
            "success": True,
            "url": f"https://{config.get('github_user', 'user')}.github.io/{config.get('repo_name', 'docs')}",
            "deployed_at": datetime.utcnow().isoformat()
        }
    
    def _deploy_notion(self, outputs: Dict, config: Dict) -> Dict:
        """Publish to Notion."""
        from notion_client import Client
        
        notion = Client(auth=config["notion_token"])
        parent_page_id = config["parent_page_id"]
        
        created_pages = []
        
        for filename, content in outputs.items():
            # Parse markdown to Notion blocks
            blocks = self._markdown_to_notion_blocks(content)
            
            page = notion.pages.create(
                parent={"page_id": parent_page_id},
                properties={
                    "title": [{"text": {"content": Path(filename).stem.replace("-", " ").title()}}]
                },
                children=blocks
            )
            
            created_pages.append(page["id"])
        
        return {
            "success": True,
            "pages": created_pages,
            "deployed_at": datetime.utcnow().isoformat()
        }

# Example publishing
publisher = DocumentationPublisher()
results = publisher.publish(outputs, {
    "deploy_to": "github_pages",
    "repo_url": "https://github.com/user/docs",
    "github_user": "user",
    "repo_name": "docs"
})

Example Usage

# Documentation generation workflow
1. Analyzer: Scan 500+ files, extract 45 endpoints, 120 classes (2 min)
2. Extractor: Parse OpenAPI spec, extract docstrings (1 min)
3. Writer: Generate 8 documentation sections (3 min)
4. Reviewer: Check accuracy, completeness, consistency (1 min)
5. Formatter: Create MkDocs structure (30 sec)
6. Publisher: Deploy to GitHub Pages (1 min)

# Total: ~8 minutes, fully automated

Pros

  • ✅ Generates comprehensive documentation automatically
  • ✅ Ensures documentation matches actual code
  • ✅ Consistent formatting and style
  • ✅ Supports multiple output formats
  • ✅ Saves hours of manual documentation work

Cons

  • ❌ Generated docs need human review for quality
  • ❌ May miss nuanced documentation needs
  • ❌ Code changes require regeneration
  • ❌ Complex codebases may produce overwhelming docs
  • ❌ AI may generate inaccurate technical details

When to Use

Use this workflow when:

  • You need to generate docs for a new codebase
  • Your documentation is outdated or incomplete
  • You want consistent documentation across projects
  • You need to document APIs quickly

Consider alternatives when:

  • You have a small, simple codebase
  • Your team has strong documentation practices
  • Documentation requires deep domain expertise
  • You need interactive API documentation (use Swagger UI directly)

Resources