112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
#!/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
|