This commit is contained in:
2025-06-08 06:41:41 +09:00
parent 582b983a32
commit ed6d6e0d47
68 changed files with 1998 additions and 1036 deletions

View File

@@ -0,0 +1,314 @@
"""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