Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:58:54 +08:00
commit 4d6408436e
32 changed files with 3539 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Module de cache pour projets GitHub"""
import json
import re
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
CACHE_DIR = Path(".claude/cache")
CACHE_FILE = CACHE_DIR / "git-projects.json"
class ProjectCache:
"""Gestion cache projets"""
def __init__(self):
self.cache = self.load()
def load(self) -> dict:
"""Charge cache depuis fichier"""
if not CACHE_FILE.exists():
CACHE_DIR.mkdir(parents=True, exist_ok=True)
return {"updated_at": None, "projects": []}
try:
with open(CACHE_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return {"updated_at": None, "projects": []}
def save(self):
"""Sauvegarde cache vers fichier"""
CACHE_DIR.mkdir(parents=True, exist_ok=True)
self.cache["updated_at"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
with open(CACHE_FILE, "w", encoding="utf-8") as f:
json.dump(self.cache, f, indent=2, ensure_ascii=False)
def find(self, query: str) -> Optional[dict]:
"""Cherche projet par titre exact ou alias (case-insensitive)"""
query_lower = query.lower()
for project in self.cache.get("projects", []):
if project["title"].lower() == query_lower:
return project
if query_lower in [alias.lower() for alias in project.get("aliases", [])]:
return project
return None
def add(self, project: dict):
"""Ajoute projet au cache avec génération aliases"""
aliases = self.generate_aliases(project["title"])
project["aliases"] = aliases
existing = [p for p in self.cache.get("projects", []) if p["id"] == project["id"]]
if not existing:
if "projects" not in self.cache:
self.cache["projects"] = []
self.cache["projects"].append(project)
self.save()
def generate_aliases(self, title: str) -> list[str]:
"""Génère aliases depuis titre
Exemples:
- "Project Alpha" → ["project", "alpha"]
- "Sprint 2024-Q1" → ["sprint", "2024", "q1"]
- "Bug Tracking" → ["bug", "tracking"]
Logique: extraire mots-clés significatifs
"""
aliases = []
words = re.findall(r'[\w]+', title.lower())
for word in words:
if len(word) >= 2 and word not in ['the', 'and', 'for']:
aliases.append(word)
return list(set(aliases))
def refresh_from_api(self, projects: list[dict]):
"""Remplace cache entièrement avec données API"""
enriched = []
for project in projects:
aliases = self.generate_aliases(project["title"])
enriched.append({
"id": project["id"],
"title": project["title"],
"number": project.get("number"),
"aliases": aliases
})
self.cache["projects"] = enriched
self.save()
def get_repo_info(self) -> str:
"""Récupère owner/repo depuis git remote"""
try:
result = subprocess.run(
["git", "remote", "get-url", "origin"],
capture_output=True,
text=True,
check=True
)
url = result.stdout.strip()
match = re.search(r'github\.com[:/](.+/.+?)(?:\.git)?$', url)
if match:
repo_full = match.group(1)
owner, _ = repo_full.split('/')
return owner
raise ValueError(f"Format URL invalide: {url}")
except Exception as e:
print(f"❌ Erreur récupération repo: {e}", file=sys.stderr)
raise