Files
gpt/python_backup/src/aigpt/docs/wiki_generator.py
2025-06-08 06:41:41 +09:00

314 lines
12 KiB
Python

"""Wiki generation utilities for ai.wiki management."""
import re
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from rich.console import Console
from .config import DocsConfig, get_ai_root
from .utils import find_project_directories
from .git_utils import pull_wiki_repository, push_wiki_repository
console = Console()
class WikiGenerator:
"""Generates wiki content from project documentation."""
def __init__(self, config: DocsConfig, ai_root: Path):
self.config = config
self.ai_root = ai_root
self.wiki_root = ai_root / "ai.wiki" if (ai_root / "ai.wiki").exists() else None
def extract_project_summary(self, project_md_path: Path) -> Dict[str, str]:
"""Extract key information from claude/projects/${repo}.md file."""
if not project_md_path.exists():
return {"title": "No documentation", "summary": "Project documentation not found", "status": "Unknown"}
try:
content = project_md_path.read_text(encoding="utf-8")
# Extract title (first # heading)
title_match = re.search(r'^# (.+)$', content, re.MULTILINE)
title = title_match.group(1) if title_match else "Unknown Project"
# Extract project overview/summary (look for specific patterns)
summary = self._extract_summary_section(content)
# Extract status information
status = self._extract_status_info(content)
# Extract key features/goals
features = self._extract_features(content)
return {
"title": title,
"summary": summary,
"status": status,
"features": features,
"last_updated": self._get_last_updated_info(content)
}
except Exception as e:
console.print(f"[yellow]Warning: Failed to parse {project_md_path}: {e}[/yellow]")
return {"title": "Parse Error", "summary": str(e), "status": "Error"}
def _extract_summary_section(self, content: str) -> str:
"""Extract summary or overview section."""
# Look for common summary patterns
patterns = [
r'## 概要\s*\n(.*?)(?=\n##|\n#|\Z)',
r'## Overview\s*\n(.*?)(?=\n##|\n#|\Z)',
r'## プロジェクト概要\s*\n(.*?)(?=\n##|\n#|\Z)',
r'\*\*目的\*\*: (.+?)(?=\n|$)',
r'\*\*中核概念\*\*:\s*\n(.*?)(?=\n##|\n#|\Z)',
]
for pattern in patterns:
match = re.search(pattern, content, re.DOTALL | re.MULTILINE)
if match:
summary = match.group(1).strip()
# Clean up and truncate
summary = re.sub(r'\n+', ' ', summary)
summary = re.sub(r'\s+', ' ', summary)
return summary[:300] + "..." if len(summary) > 300 else summary
# Fallback: first paragraph after title
lines = content.split('\n')
summary_lines = []
found_content = False
for line in lines:
line = line.strip()
if not line:
if found_content and summary_lines:
break
continue
if line.startswith('#'):
found_content = True
continue
if found_content and not line.startswith('*') and not line.startswith('-'):
summary_lines.append(line)
if len(' '.join(summary_lines)) > 200:
break
return ' '.join(summary_lines)[:300] + "..." if summary_lines else "No summary available"
def _extract_status_info(self, content: str) -> str:
"""Extract status information."""
# Look for status patterns
patterns = [
r'\*\*状況\*\*: (.+?)(?=\n|$)',
r'\*\*Status\*\*: (.+?)(?=\n|$)',
r'\*\*現在の状況\*\*: (.+?)(?=\n|$)',
r'- \*\*状況\*\*: (.+?)(?=\n|$)',
]
for pattern in patterns:
match = re.search(pattern, content)
if match:
return match.group(1).strip()
return "No status information"
def _extract_features(self, content: str) -> List[str]:
"""Extract key features or bullet points."""
features = []
# Look for bullet point lists
lines = content.split('\n')
in_list = False
for line in lines:
line = line.strip()
if line.startswith('- ') or line.startswith('* '):
feature = line[2:].strip()
if len(feature) > 10 and not feature.startswith('**'): # Skip metadata
features.append(feature)
in_list = True
if len(features) >= 5: # Limit to 5 features
break
elif in_list and not line:
break
return features
def _get_last_updated_info(self, content: str) -> str:
"""Extract last updated information."""
patterns = [
r'生成日時: (.+?)(?=\n|$)',
r'最終更新: (.+?)(?=\n|$)',
r'Last updated: (.+?)(?=\n|$)',
]
for pattern in patterns:
match = re.search(pattern, content)
if match:
return match.group(1).strip()
return "Unknown"
def generate_project_wiki_page(self, project_name: str, project_info: Dict[str, str]) -> str:
"""Generate wiki page for a single project."""
config_info = self.config.get_project_info(project_name)
content = f"""# {project_name}
## 概要
{project_info['summary']}
## プロジェクト情報
- **タイプ**: {config_info.type if config_info else 'Unknown'}
- **説明**: {config_info.text if config_info else 'No description'}
- **ステータス**: {config_info.status if config_info else project_info.get('status', 'Unknown')}
- **ブランチ**: {config_info.branch if config_info else 'main'}
- **最終更新**: {project_info.get('last_updated', 'Unknown')}
## 主な機能・特徴
"""
features = project_info.get('features', [])
if features:
for feature in features:
content += f"- {feature}\n"
else:
content += "- 情報なし\n"
content += f"""
## リンク
- **Repository**: https://git.syui.ai/ai/{project_name}
- **Project Documentation**: [claude/projects/{project_name}.md](https://git.syui.ai/ai/ai/src/branch/main/claude/projects/{project_name}.md)
- **Generated Documentation**: [{project_name}/claude.md](https://git.syui.ai/ai/{project_name}/src/branch/main/claude.md)
---
*このページは claude/projects/{project_name}.md から自動生成されました*
"""
return content
def generate_wiki_home_page(self, project_summaries: Dict[str, Dict[str, str]]) -> str:
"""Generate the main Home.md page with all project summaries."""
content = """# AI Ecosystem Wiki
AI生態系プロジェクトの概要とドキュメント集約ページです。
## プロジェクト一覧
"""
# Group projects by type
project_groups = {}
for project_name, info in project_summaries.items():
config_info = self.config.get_project_info(project_name)
project_type = config_info.type if config_info else 'other'
if isinstance(project_type, list):
project_type = project_type[0] # Use first type
if project_type not in project_groups:
project_groups[project_type] = []
project_groups[project_type].append((project_name, info))
# Generate sections by type
type_names = {
'ai': '🧠 AI・知能システム',
'gpt': '🤖 自律・対話システム',
'os': '💻 システム・基盤',
'card': '🎮 ゲーム・エンターテイメント',
'shell': '⚡ ツール・ユーティリティ',
'other': '📦 その他'
}
for project_type, projects in project_groups.items():
type_display = type_names.get(project_type, f'📁 {project_type}')
content += f"### {type_display}\n\n"
for project_name, info in projects:
content += f"#### [{project_name}](auto/{project_name}.md)\n"
content += f"{info['summary'][:150]}{'...' if len(info['summary']) > 150 else ''}\n\n"
# Add quick status
config_info = self.config.get_project_info(project_name)
if config_info:
content += f"**Status**: {config_info.status} \n"
content += f"**Links**: [Repo](https://git.syui.ai/ai/{project_name}) | [Docs](https://git.syui.ai/ai/{project_name}/src/branch/main/claude.md)\n\n"
content += """
---
## ディレクトリ構成
- `auto/` - 自動生成されたプロジェクト概要
- `claude/` - Claude Code作業記録
- `manual/` - 手動作成ドキュメント
---
*このページは ai.json と claude/projects/ から自動生成されました*
*最終更新: {last_updated}*
""".format(last_updated=self._get_current_timestamp())
return content
def _get_current_timestamp(self) -> str:
"""Get current timestamp."""
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def update_wiki_auto_directory(self, auto_pull: bool = True) -> Tuple[bool, List[str]]:
"""Update the auto/ directory with project summaries."""
if not self.wiki_root:
return False, ["ai.wiki directory not found"]
# Pull latest changes from wiki repository first
if auto_pull:
success, message = pull_wiki_repository(self.wiki_root)
if not success:
console.print(f"[yellow]⚠️ Wiki pull failed: {message}[/yellow]")
console.print("[dim]Continuing with local wiki update...[/dim]")
else:
console.print(f"[green]✅ Wiki repository updated[/green]")
auto_dir = self.wiki_root / "auto"
auto_dir.mkdir(exist_ok=True)
# Get claude/projects directory
claude_projects_dir = self.ai_root / "claude" / "projects"
if not claude_projects_dir.exists():
return False, [f"claude/projects directory not found: {claude_projects_dir}"]
project_summaries = {}
updated_files = []
console.print("[blue]📋 Extracting project summaries from claude/projects/...[/blue]")
# Process all projects from ai.json
for project_name in self.config.list_projects():
project_md_path = claude_projects_dir / f"{project_name}.md"
# Extract summary from claude/projects/${project}.md
project_info = self.extract_project_summary(project_md_path)
project_summaries[project_name] = project_info
# Generate individual project wiki page
wiki_content = self.generate_project_wiki_page(project_name, project_info)
wiki_file_path = auto_dir / f"{project_name}.md"
try:
wiki_file_path.write_text(wiki_content, encoding="utf-8")
updated_files.append(f"auto/{project_name}.md")
console.print(f"[green]✓ Generated auto/{project_name}.md[/green]")
except Exception as e:
console.print(f"[red]✗ Failed to write auto/{project_name}.md: {e}[/red]")
# Generate Home.md
try:
home_content = self.generate_wiki_home_page(project_summaries)
home_path = self.wiki_root / "Home.md"
home_path.write_text(home_content, encoding="utf-8")
updated_files.append("Home.md")
console.print(f"[green]✓ Generated Home.md[/green]")
except Exception as e:
console.print(f"[red]✗ Failed to write Home.md: {e}[/red]")
return True, updated_files