system/scripts/docs_index.py
#!/usr/bin/env python3
"""
Docs Index Generator

Генерирует INDEX.md с иерархией всех документов платформы.

Режимы:
    --all           Вся платформа (архитектор)
    --project NAME  Только проект
    --section NAME  Только секция (architect, system, etc.)

Использование:
    python3 docs_index.py --all
    python3 docs_index.py --project pirotehnika
    python3 docs_index.py --section architect
"""

import os
import re
import argparse
from pathlib import Path
from datetime import datetime

WORKSPACE = os.environ.get('WORKSPACE', '/opt/claude-workspace')
EXCLUDE_DIRS = {'node_modules', 'venv', '.venv', '__pycache__', '.git', 'vendor'}
EXCLUDE_FILES = {'package-lock.json', 'yarn.lock'}


def get_doc_info(filepath: Path) -> dict:
    """Извлекает метаданные из .md файла"""
    info = {
        'path': str(filepath),
        'name': filepath.name,
        'title': filepath.stem,
        'version': None,
        'description': None
    }

    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            content = f.read(2000)  # Первые 2KB

        # Заголовок
        title_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
        if title_match:
            info['title'] = title_match.group(1).strip()

        # Версия
        version_match = re.search(r'\*\*Версия:\*\*\s*(\S+)', content)
        if version_match:
            info['version'] = version_match.group(1)

        # Первый абзац после заголовка как описание
        desc_match = re.search(r'^#.+\n\n(.+?)(?:\n\n|$)', content, re.MULTILINE | re.DOTALL)
        if desc_match:
            desc = desc_match.group(1).strip()
            desc = re.sub(r'\*\*[^*]+\*\*[:\s]*\S+\s*', '', desc)  # Убираем **Версия:** и т.д.
            desc = desc.strip()
            if desc and len(desc) > 10:
                info['description'] = desc[:100] + '...' if len(desc) > 100 else desc

    except Exception:
        pass

    return info


def scan_directory(root_path: Path, prefix: str = "") -> list:
    """Рекурсивно сканирует директорию"""
    items = []

    if not root_path.exists():
        return items

    entries = sorted(root_path.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower()))

    for entry in entries:
        if entry.name.startswith('.') or entry.name in EXCLUDE_DIRS:
            continue

        if entry.is_dir():
            # Проверяем есть ли .md файлы внутри
            has_md = any(entry.rglob('*.md'))
            if has_md:
                items.append({
                    'type': 'dir',
                    'name': entry.name,
                    'path': str(entry.relative_to(WORKSPACE)),
                    'children': scan_directory(entry, prefix + "  ")
                })
        elif entry.suffix.lower() == '.md':
            info = get_doc_info(entry)
            info['type'] = 'file'
            info['rel_path'] = str(entry.relative_to(WORKSPACE))
            items.append(info)

    return items


def render_tree(items: list, indent: int = 0) -> str:
    """Рендерит дерево в markdown"""
    lines = []
    prefix = "  " * indent

    for item in items:
        if item['type'] == 'dir':
            lines.append(f"{prefix}📁 **{item['name']}/**")
            if item.get('children'):
                lines.append(render_tree(item['children'], indent + 1))
        else:
            # Файл
            version = f" (v{item['version']})" if item.get('version') else ""
            desc = f" — {item['description']}" if item.get('description') else ""
            link = f"[{item['title']}]({item['rel_path']})"
            lines.append(f"{prefix}📄 {link}{version}{desc}")

    return "\n".join(lines)


def generate_index(mode: str = 'all', target: str = None) -> str:
    """Генерирует INDEX.md"""

    now = datetime.now().strftime('%Y-%m-%d %H:%M')

    header = f"""# Документация платформы

**Сгенерировано:** {now}
**Режим:** {mode}
"""

    if mode == 'all':
        # Вся платформа
        sections = [
            ('architect', 'Методология'),
            ('system', 'Система'),
            ('infra', 'Инфраструктура'),
            ('projects', 'Проекты'),
        ]

        content = header + "\n---\n\n"

        # Корневые документы
        root_docs = [f for f in Path(WORKSPACE).iterdir()
                     if f.suffix.lower() == '.md' and not f.name.startswith('.')]
        if root_docs:
            content += "## Корневые документы\n\n"
            for doc in sorted(root_docs):
                info = get_doc_info(doc)
                version = f" (v{info['version']})" if info.get('version') else ""
                content += f"📄 [{info['title']}]({doc.name}){version}\n\n"

        for section, title in sections:
            section_path = Path(WORKSPACE) / section
            if section_path.exists():
                items = scan_directory(section_path)
                if items:
                    content += f"## {title}\n\n"
                    content += render_tree(items) + "\n\n"

    elif mode == 'project':
        # Только проект
        project_path = Path(WORKSPACE) / 'projects' / target
        if not project_path.exists():
            return f"# Ошибка\n\nПроект '{target}' не найден."

        content = header + f"**Проект:** {target}\n\n---\n\n"
        items = scan_directory(project_path)
        content += render_tree(items)

    elif mode == 'section':
        # Только секция
        section_path = Path(WORKSPACE) / target
        if not section_path.exists():
            return f"# Ошибка\n\nСекция '{target}' не найдена."

        content = header + f"**Секция:** {target}\n\n---\n\n"
        items = scan_directory(section_path)
        content += render_tree(items)

    return content


def main():
    parser = argparse.ArgumentParser(description='Docs Index Generator')
    parser.add_argument('--all', action='store_true', help='Вся платформа')
    parser.add_argument('--project', '-p', help='Только проект')
    parser.add_argument('--section', '-s', help='Только секция')
    parser.add_argument('--output', '-o', help='Файл вывода (по умолчанию stdout)')
    parser.add_argument('--save', action='store_true', help='Сохранить в INDEX.md')

    args = parser.parse_args()

    if args.project:
        content = generate_index('project', args.project)
    elif args.section:
        content = generate_index('section', args.section)
    else:
        content = generate_index('all')

    if args.save:
        output_path = Path(WORKSPACE) / 'INDEX.md'
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(content)
        print(f"Сохранено: {output_path}")
    elif args.output:
        with open(args.output, 'w', encoding='utf-8') as f:
            f.write(content)
        print(f"Сохранено: {args.output}")
    else:
        print(content)


if __name__ == '__main__':
    main()