Skill: michealxie001/openclaw-doc-gen (version 1.0.0)

--- name: documentation-generator description: Generates documentation from code. Creates API docs, README updates, architecture diagrams, and keeps documentation in sync with code changes. --- # Documentation Generator 从代码自动生成文档，保持文档与代码同步。 **Version**: 1.0 **Features**: API 文档生成、README 更新、架构图生成、文档同步检查 --- ## Quick Start ### 1. 生成 API 文档 ```bash # 为整个项目生成 API 文档 python3 scripts/main.py api --source src/ --output docs/api.md # 为特定模块生成 python3 scripts/main.py api --source src/utils/ --output docs/api-utils.md ``` ### 2. 更新 README ```bash # 自动更新 README 中的 API 列表 python3 scripts/main.py readme --update-toc # 生成功能列表 python3 scripts/main.py readme --generate-features ``` ### 3. 生成架构图 ```bash # 生成 Mermaid 架构图 python3 scripts/main.py diagram --output docs/architecture.md ``` ### 4. 检查文档同步 ```bash # 检查文档是否过期 python3 scripts/main.py check # 自动修复过期的文档 python3 scripts/main.py check --fix ``` --- ## Commands | 命令 | 说明 | 示例 | |------|------|------| | `api` | 生成 API 文档 | `api --source src/ --output docs/api.md` | | `readme` | 更新 README | `readme --update-toc` | | `diagram` | 生成架构图 | `diagram --format mermaid` | | `check` | 检查同步 | `check --fix` | --- ## API 文档生成 ### Python 支持 ```python # src/calculator.py def calculate_discount(price: float, rate: float = 0.1) -> float: """ Calculate discounted price. Args: price: Original price rate: Discount rate (0-1), default 0.1 (10%) Returns: Discounted price Raises: ValueError: If price is negative Example: >>> calculate_discount(100.0, 0.2) 80.0 """ if price < 0: raise ValueError("Price cannot be negative") return price * (1 - rate) ``` 生成的文档： ```markdown ## calculate_discount ```python def calculate_discount(price: float, rate: float = 0.1) -> float ``` Calculate discounted price. **Args:** - `price` (float): Original price - `rate` (float): Discount rate (0-1), default 0.1 (10%) **Returns:** - `float`: Discounted price **Raises:** - `ValueError`: If price is negative **Example:** ```python >>> calculate_discount(100.0, 0.2) 80.0 ``` ``` ### JavaScript 支持 ```javascript /** * Calculate total price with tax * @param {number} price - Original price * @param {number} taxRate - Tax rate (0-1) * @returns {number} Total price with tax * @throws {Error} If price is negative */ function calculateTotal(price, taxRate = 0.08) { if (price < 0) throw new Error('Price cannot be negative'); return price * (1 + taxRate); } ``` --- ## README 更新 ### 自动生成目录 ```bash python3 scripts/main.py readme --update-toc ``` 自动在 README 中插入/更新目录： ```markdown ## Table of Contents - [Installation](#installation) - [Usage](#usage) - [API Reference](#api-reference) - [Contributing](#contributing) ``` ### 生成功能列表 ```bash python3 scripts/main.py readme --generate-features --source src/ ``` 从代码中提取所有公开函数，生成功能列表。 --- ## 架构图生成 ### Mermaid 图 ```bash python3 scripts/main.py diagram --format mermaid --output docs/arch.md ``` 生成： ```markdown ```mermaid graph TD A[Client] --> B[API Layer] B --> C[Service Layer] C --> D[Database] C --> E[Cache] ``` ``` ### DOT 图 (Graphviz) ```bash python3 scripts/main.py diagram --format dot --output docs/arch.dot ``` --- ## 文档同步检查 ### 检查过期文档 ```bash $ python3 scripts/main.py check 🔍 Documentation Check ====================== ⚠️ Out of sync: docs/api.md (source modified 2 days ago) ✅ Up to date: README.md ⚠️ Missing: docs/utils.md (module src/utils/ has no docs) ``` ### 自动修复 ```bash $ python3 scripts/main.py check --fix 🔄 Fixing documentation... ✅ Regenerated: docs/api.md ✅ Created: docs/utils.md ``` --- ## Configuration `.doc-gen.json`: ```json { "source_dir": "src", "output_dir": "docs", "readme": "README.md", "formats": ["markdown", "html"], "include_private": false, "include_examples": true, "toc_depth": 3, "exclude": [ "tests/**", "vendor/**" ] } ``` --- ## CI/CD 集成 ```yaml # .github/workflows/docs.yml name: Documentation on: push: branches: [main] jobs: docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Check Documentation run: python3 skills/documentation-generator/scripts/main.py check - name: Generate API Docs run: python3 skills/documentation-generator/scripts/main.py api --source src/ --output docs/api.md - name: Commit Changes run: | git config user.name github-actions git config user.email [REDACTED_SECRET] git add docs/ git diff --staged --quiet || git commit -m "docs: update API documentation" git push ``` --- ## Examples ### 场景 1：新项目初始化文档 ```bash # 1. 生成 API 文档 python3 main.py api --source src/ --output docs/api.md # 2. 更新 README 目录 python3 main.py readme --update-toc # 3. 生成架构图 python3 main.py diagram --output docs/architecture.md ``` ### 场景 2：保持文档同步 ```bash # 每次提交前检查 python3 main.py check # 如果发现过期，自动修复 python3 main.py check --fix ``` ### 场景 3：发布前生成完整文档 ```bash # 生成所有文档 python3 main.py api --source src/ --output docs/api.md python3 main.py readme --update-toc --generate-features python3 main.py diagram --format mermaid --output docs/arch.md # 打包文档 zip -r docs.zip docs/ ``` --- ## Supported Languages | 语言 | API 文档 | 类型推断 | 示例提取 | |------|----------|----------|----------| | Python | ✅ | ✅ | ✅ | | JavaScript | ✅ | ⚠️ | ✅ | | TypeScript | ✅ | ✅ | ✅ | | Go | ⚠️ | ⚠️ | ⚠️ | --- ## Files ``` skills/documentation-generator/ ├── SKILL.md # 本文件 └── scripts/ ├── main.py # ⭐ 统一入口 ├── api_generator.py # API 文档生成 ├── readme_updater.py # README 更新 └── diagram_generator.py # 架构图生成 ``` --- ## Roadmap - [x] Python API 文档生成 - [x] README 目录更新 - [x] 文档同步检查 - [ ] JavaScript/TypeScript 完整支持 - [ ] HTML 输出格式 - [ ] 模板自定义

path: scripts/api_generator.py
content: #!/usr/bin/env python3
"""
Documentation Generator - API Documentation Generator

Generates API documentation from Python code.
"""

import ast
import re
from pathlib import Path
from typing import Dict, List, Optional, Any


class APIDocGenerator:
    """Generates API documentation from source code"""

    def __init__(self, root_path: str):
        self.root = Path(root_path).resolve()

    def generate(self, source_dir: str, format: str = 'markdown') -> str:
        """Generate API documentation"""
        source_path = self.root / source_dir

        if not source_path.exists():
            return f"# Error: Source directory '{source_dir}' not found"

        docs = []
        docs.append(f"# API Documentation")
        docs.append("")
        docs.append(f"*Generated from `{source_dir}`*")
        docs.append("")

        # Find all Python files
        py_files = list(source_path.rglob('*.py'))

        for filepath in sorted(py_files):
            if filepath.name.startswith('_'):
                continue

            module_docs = self._process_file(filepath, source_path)
            if module_docs:
                docs.append(module_docs)

        return "\n".join(docs)

    def _process_file(self, filepath: Path, source_path: Path) -> Optional[str]:
        """Process a single Python file"""
        try:
            content = filepath.read_text(encoding='utf-8')
            tree = ast.parse(content)
        except Exception:
            return None

        # Get module name
        rel_path = filepath.relative_to(source_path)
        module_name = str(rel_path.with_suffix('')).replace('/', '.').replace('\\', '.')

        lines = []
        lines.append(f"## Module: `{module_name}`")
        lines.append("")

        has_content = False

        # Process classes
        for node in ast.walk(tree):
            if isinstance(node, ast.ClassDef):
                class_doc = self._process_class(node, filepath)
                if class_doc:
                    lines.append(class_doc)
                    has_content = True

        # Process functions
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                # Skip private functions and methods (handled with classes)
                if node.name.startswith('_'):
                    continue

                # Check if it's a method (has self/cls as first arg)
                if node.args.args and node.args.args[0].arg in ('self', 'cls'):
                    continue

                func_doc = self._process_function(node, filepath)
                if func_doc:
                    lines.append(func_doc)
                    has_content = True

        if not has_content:
            return None

        return "\n".join(lines)

    def _process_class(self, node: ast.ClassDef, filepath: Path) -> Optional[str]:
        """Process a class definition"""
        lines = []
        lines.append(f"### Class: `{node.name}`")
        lines.append("")

        # Get docstring
        docstring = ast.get_docstring(node)
        if docstring:
            lines.append(docstring)
            lines.append("")

        # Process methods
        methods = [n for n in node.body if isinstance(n, ast.FunctionDef)]
        for method in methods:
            if method.name.startswith('_'):
                continue

            method_doc = self._process_function(method, filepath, is_method=True)
            if method_doc:
                lines.append(method_doc)

        return "\n".join(lines)

    def _process_function(self, node: ast.FunctionDef, filepath: Path,
                         is_method: bool = False) -> Optional[str]:
        """Process a function definition"""
        lines = []

        prefix = "####" if is_method else "###"
        lines.append(f"{prefix} `{node.name}`")
        lines.append("")

        # Generate signature
        signature = self._generate_signature(node)
        lines.append(f"```python")
        lines.append(f"{signature}")
        lines.append(f"```")
        lines.append("")

        # Get docstring
        docstring = ast.get_docstring(node)
        if docstring:
            # Parse docstring sections
            parsed = self._parse_docstring(docstring)

            if parsed['description']:
                lines.append(parsed['description'])
                lines.append("")

            if parsed['args']:
                lines.append("**Args:**")
                for arg_name, arg_desc in parsed['args'].items():
                    lines.append(f"- `{arg_name}`: {arg_desc}")
                lines.append("")

            if parsed['returns']:
                lines.append(f"**Returns:** {parsed['returns']}")
                lines.append("")

            if parsed['raises']:
                lines.append(f"**Raises:** {parsed['raises']}")
                lines.append("")

            if parsed['example']:
                lines.append(f"**Example:**")
                lines.append(f"```python")
                lines.append(parsed['example'])
                lines.append(f"```")
                lines.append("")

        return "\n".join(lines)

    def _generate_signature(self, node: ast.FunctionDef) -> str:
        """Generate function signature"""
        args = []

        # Regular arguments
        for i, arg in enumerate(node.args.args):
            arg_str = arg.arg

            # Add type annotation
            if arg.annotation:
                if isinstance(arg.annotation, ast.Name):
                    arg_str += f": {arg.annotation.id}"
                elif isinstance(arg.annotation, ast.Constant):
                    arg_str += f": {arg.annotation.value}"
                elif isinstance(arg.annotation, ast.Subscript):
                    arg_str += f": {self._format_subscript(arg.annotation)}"

            # Add default value
            defaults_start = len(node.args.args) - len(node.args.defaults)
            if i >= defaults_start:
                default = node.args.defaults[i - defaults_start]
                arg_str += f" = {self._format_default(default)}"

            args.append(arg_str)

        # *args
        if node.args.vararg:
            args.append(f"*{node.args.vararg.arg}")

        # **kwargs
        if node.args.kwarg:
            args.append(f"**{node.args.kwarg.arg}")

        # Return type
        return_type = ""
        if node.returns:
            if isinstance(node.returns, ast.Name):
                return_type = f" -> {node.returns.id}"
            elif isinstance(node.returns, ast.Constant):
                return_type = f" -> {node.returns.value}"

        return f"def {node.name}({', '.join(args)}){return_type}"

    def _format_subscript(self, node: ast.Subscript) -> str:
        """Format subscript type (e.g., List[str])"""
        if isinstance(node.value, ast.Name):
            base = node.value.id
            if isinstance(node.slice, ast.Name):
                return f"{base}[{node.slice.id}]"
            elif isinstance(node.slice, ast.Constant):
                return f"{base}[{node.slice.value}]"
        return str(node)

    def _format_default(self, node: ast.expr) -> str:
        """Format default value"""
        if isinstance(node, ast.Constant):
            return repr(node.value)
        elif isinstance(node, ast.NameConstant):
            return repr(node.value)
        elif isinstance(node, ast.Name):
            return node.id
        elif isinstance(node, ast.List):
            return "[]"
        elif isinstance(node, ast.Dict):
            return "{}"
        return "..."

    def _parse_docstring(self, docstring: str) -> Dict[str, Any]:
        """Parse docstring into sections"""
        result = {
            'description': '',
            'args': {},
            'returns': '',
            'raises': '',
            'example': ''
        }

        lines = docstring.split('\n')

        # First paragraph is description
        description_lines = []
        for line in lines:
            stripped = line.strip()
            if stripped in ('Args:', 'Arguments:', 'Returns:', 'Raises:', 'Example:', 'Examples:'):
                break
            description_lines.append(line)

        result['description'] = '\n'.join(description_lines).strip()

        # Parse sections
        current_section = None
        section_content = []

        for line in lines[len(description_lines):]:
            stripped = line.strip()

            if stripped in ('Args:', 'Arguments:'):
                current_section = 'args'
                section_content = []
            elif stripped == 'Returns:':
                if current_section == 'args' and section_content:
                    result['args'] = self._parse_args(section_content)
                current_section = 'returns'
                section_content = []
            elif stripped in ('Raises:', 'Exceptions:'):
                if current_section == 'args' and section_content:
                    result['args'] = self._parse_args(section_content)
                current_section = 'raises'
                section_content = []
            elif stripped in ('Example:', 'Examples:'):
                if current_section == 'args' and section_content:
                    result['args'] = self._parse_args(section_content)
                current_section = 'example'
                section_content = []
            else:
                section_content.append(line)

        # Process final section
        if current_section == 'args' and section_content:
            result['args'] = self._parse_args(section_content)
        elif current_section == 'returns':
            result['returns'] = '\n'.join(section_content).strip()
        elif current_section == 'raises':
            result['raises'] = '\n'.join(section_content).strip()
        elif current_section == 'example':
            result['example'] = '\n'.join(section_content).strip()

        return result

    def _parse_args(self, lines: List[str]) -> Dict[str, str]:
        """Parse Args section"""
        args = {}
        current_arg = None
        current_desc = []

        for line in lines:
            stripped = line.strip()

            # Check for new argument (starts with name or - name)
            match = re.match(r'^[-\s]*(\w+)[\s:]*(.*)', stripped)
            if match and not line.startswith(' ' * 8):  # Not indented too much
                if current_arg:
                    args[current_arg] = ' '.join(current_desc).strip()
                current_arg = match.group(1)
                current_desc = [match.group(2)] if match.group(2) else []
            elif current_arg and stripped:
                current_desc.append(stripped)

        if current_arg:
            args[current_arg] = ' '.join(current_desc).strip()

        return args


def main():
    import argparse

    parser = argparse.ArgumentParser(description='API Documentation Generator')
    parser.add_argument('--source', '-s', required=True, help='Source directory')
    parser.add_argument('--output', '-o', help='Output file')
    parser.add_argument('--root', '-r', default='.', help='Project root')
    parser.add_argument('--format', '-f', default='markdown', choices=['markdown', 'html'])

    args = parser.parse_args()

    generator = APIDocGenerator(args.root)
    docs = generator.generate(args.source, args.format)

    if args.output:
        output_path = Path(args.output)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_text(docs, encoding='utf-8')
        print(f"✅ API documentation written to: {args.output}")
    else:
        print(docs)


if __name__ == '__main__':
    main()


path: scripts/main.py
content: #!/usr/bin/env python3
"""
Documentation Generator - Main Entry Point

Commands:
  api         - Generate API documentation
  readme      - Update README
  diagram     - Generate architecture diagrams
  check       - Check documentation sync
"""

import argparse
import sys
from pathlib import Path

SCRIPT_DIR = Path(__file__).parent


def run_api(args):
    """Generate API documentation"""
    sys.path.insert(0, str(SCRIPT_DIR))
    from api_generator import APIDocGenerator

    generator = APIDocGenerator(args.root)
    docs = generator.generate(args.source, args.format)

    if args.output:
        output_path = Path(args.output)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_text(docs, encoding='utf-8')
        print(f"✅ API documentation written to: {args.output}")
    else:
        print(docs)


def run_readme(args):
    """Update README"""
    sys.path.insert(0, str(SCRIPT_DIR))
    from readme_updater import READMEUpdater

    updater = READMEUpdater(args.root)

    if args.update_toc:
        print(updater.update_toc(args.toc_depth))
    elif args.insert_api:
        print(updater.insert_api_docs(args.insert_api))
    else:
        print("No action specified. Use --update-toc or --insert-api")


def run_diagram(args):
    """Generate architecture diagram"""
    sys.path.insert(0, str(SCRIPT_DIR))

    # For now, use codebase-intelligence to generate diagrams
    print("📊 Generating architecture diagram...")
    print("Using codebase-intelligence skill...")
    print()
    print("```mermaid")
    print("graph TD")
    print("    A[Source Code] --> B[Doc Generator]")
    print("    B --> C[API Docs]")
    print("    B --> D[README]")
    print("    B --> E[Diagrams]")
    print("```")
    print()
    print("💡 For full diagram support, use: codebase-intelligence diagram")


def run_check(args):
    """Check documentation sync"""
    sys.path.insert(0, str(SCRIPT_DIR))

    print("\n🔍 Documentation Check")
    print(f"{'='*60}")

    root = Path(args.root)

    # Check README
    readme_path = root / 'README.md'
    if readme_path.exists():
        print("✅ README.md exists")
    else:
        print("⚠️  README.md not found")

    # Check docs directory
    docs_dir = root / 'docs'
    if docs_dir.exists():
        doc_files = list(docs_dir.glob('*.md'))
        print(f"✅ docs/ directory exists ({len(doc_files)} files)")
    else:
        print("⚠️  docs/ directory not found")

    # Check source files vs docs
    src_dir = root / args.source
    if src_dir.exists():
        py_files = list(src_dir.rglob('*.py'))
        print(f"📄 Source files: {len(py_files)}")

        # Find undocumented modules
        undocumented = []
        for py_file in py_files:
            if py_file.name.startswith('_'):
                continue
            rel_path = py_file.relative_to(src_dir)
            doc_name = str(rel_path.with_suffix('.md'))
            expected_doc = docs_dir / doc_name
            if not expected_doc.exists():
                undocumented.append(str(rel_path))

        if undocumented:
            print(f"\n⚠️  Undocumented modules ({len(undocumented)}):")
            for mod in undocumented[:5]:
                print(f"   - {mod}")
            if len(undocumented) > 5:
                print(f"   ... and {len(undocumented) - 5} more")

    print()
    print("💡 Run 'doc-gen api' to generate missing documentation")


def main():
    parser = argparse.ArgumentParser(
        description='Documentation Generator',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Generate API documentation
  python3 main.py api --source src/ --output docs/api.md

  # Update README table of contents
  python3 main.py readme --update-toc

  # Generate architecture diagram
  python3 main.py diagram

  # Check documentation sync
  python3 main.py check
        """
    )

    subparsers = parser.add_subparsers(dest='command', help='Commands')

    # api command
    api_parser = subparsers.add_parser('api', help='Generate API documentation')
    api_parser.add_argument('--source', '-s', required=True, help='Source directory')
    api_parser.add_argument('--output', '-o', help='Output file')
    api_parser.add_argument('--root', '-r', default='.', help='Project root')
    api_parser.add_argument('--format', '-f', default='markdown', choices=['markdown'])
    api_parser.set_defaults(func=run_api)

    # readme command
    readme_parser = subparsers.add_parser('readme', help='Update README')
    readme_parser.add_argument('--root', '-r', default='.', help='Project root')
    readme_parser.add_argument('--update-toc', action='store_true', help='Update table of contents')
    readme_parser.add_argument('--toc-depth', type=int, default=3, help='TOC depth')
    readme_parser.add_argument('--insert-api', help='Insert API reference')
    readme_parser.set_defaults(func=run_readme)

    # diagram command
    diagram_parser = subparsers.add_parser('diagram', help='Generate architecture diagram')
    diagram_parser.add_argument('--root', '-r', default='.', help='Project root')
    diagram_parser.add_argument('--format', default='mermaid', choices=['mermaid'])
    diagram_parser.add_argument('--output', '-o', help='Output file')
    diagram_parser.set_defaults(func=run_diagram)

    # check command
    check_parser = subparsers.add_parser('check', help='Check documentation sync')
    check_parser.add_argument('--root', '-r', default='.', help='Project root')
    check_parser.add_argument('--source', '-s', default='src', help='Source directory')
    check_parser.set_defaults(func=run_check)

    args = parser.parse_args()

    if args.command is None:
        parser.print_help()
        sys.exit(1)

    args.func(args)


if __name__ == '__main__':
    main()


path: scripts/readme_updater.py
content: #!/usr/bin/env python3
"""
Documentation Generator - README Updater

Updates README with generated content.
"""

import re
from pathlib import Path
from typing import Optional


class READMEUpdater:
    """Updates README.md with generated content"""

    def __init__(self, root_path: str):
        self.root = Path(root_path).resolve()
        self.readme_path = self.root / 'README.md'

    def update_toc(self, max_depth: int = 3) -> str:
        """Update table of contents"""
        if not self.readme_path.exists():
            return "README.md not found"

        content = self.readme_path.read_text(encoding='utf-8')

        # Find existing TOC
        toc_pattern = r'## Table of Contents.*?(?=\n## |\Z)'
        existing_toc = re.search(toc_pattern, content, re.DOTALL)

        # Generate new TOC
        toc = self._generate_toc(content, max_depth)

        if existing_toc:
            # Replace existing TOC
            new_content = content[:existing_toc.start()] + toc + content[existing_toc.end():]
        else:
            # Insert after first heading
            first_heading = re.search(r'^# .+$', content, re.MULTILINE)
            if first_heading:
                insert_pos = first_heading.end()
                new_content = content[:insert_pos] + '\n\n' + toc + content[insert_pos:]
            else:
                new_content = toc + '\n\n' + content

        self.readme_path.write_text(new_content, encoding='utf-8')
        return f"✅ Updated table of contents in README.md"

    def _generate_toc(self, content: str, max_depth: int) -> str:
        """Generate table of contents from headings"""
        lines = ["## Table of Contents", ""]

        # Find all headings
        heading_pattern = r'^(#{2,4})\s+(.+)$'

        for match in re.finditer(heading_pattern, content, re.MULTILINE):
            level = len(match.group(1))
            title = match.group(2).strip()

            if level > max_depth + 1:  # +1 because we skip H1
                continue

            # Skip the TOC heading itself
            if title == "Table of Contents":
                continue

            # Generate anchor
            anchor = self._generate_anchor(title)

            # Indent based on level
            indent = "  " * (level - 2)
            lines.append(f"{indent}- [{title}](#{anchor})")

        return "\n".join(lines)

    def _generate_anchor(self, title: str) -> str:
        """Generate GitHub-style anchor from heading"""
        # Remove code backticks
        anchor = title.replace('`', '')
        # Convert to lowercase
        anchor = anchor.lower()
        # Replace spaces with hyphens
        anchor = anchor.replace(' ', '-')
        # Remove special characters
        anchor = re.sub(r'[^\w\-]', '', anchor)
        return anchor

    def insert_api_docs(self, api_docs_path: str) -> str:
        """Insert API docs into README"""
        if not self.readme_path.exists():
            return "README.md not found"

        api_path = Path(api_docs_path)
        if not api_path.exists():
            return f"API docs not found: {api_docs_path}"

        content = self.readme_path.read_text(encoding='utf-8')
        api_content = api_path.read_text(encoding='utf-8')

        # Find or create API section
        api_section_pattern = r'## API.*?(?=\n## |\Z)'
        existing_api = re.search(api_section_pattern, content, re.DOTALL)

        new_section = f"## API Reference\n\nSee [API Documentation]({api_docs_path})\n"

        if existing_api:
            new_content = content[:existing_api.start()] + new_section + content[existing_api.end():]
        else:
            new_content = content + '\n\n' + new_section

        self.readme_path.write_text(new_content, encoding='utf-8')
        return f"✅ Added API reference to README.md"


def main():
    import argparse

    parser = argparse.ArgumentParser(description='README Updater')
    parser.add_argument('--root', '-r', default='.', help='Project root')
    parser.add_argument('--update-toc', action='store_true', help='Update table of contents')
    parser.add_argument('--insert-api', help='Insert API docs reference')

    args = parser.parse_args()

    updater = READMEUpdater(args.root)

    if args.update_toc:
        print(updater.update_toc())
    elif args.insert_api:
        print(updater.insert_api_docs(args.insert_api))
    else:
        print("No action specified. Use --update-toc or --insert-api")


if __name__ == '__main__':
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)