gitgud-ai / app /services /convention_service.py
CodeCommunity's picture
Create convention_service.py
a5ac098 verified
Raw History Blame Contribute Delete
3.96 kB
import logging
import re
import json
from typing import Dict, Any, Optional
import httpx
from app.core.model_loader import llm_engine
logger = logging.getLogger(__name__)
class ConventionService:
def __init__(self):
self.cache: Dict[str, Dict[str, Any]] = {}
async def get_conventions(self, owner: str, repo: str) -> Dict[str, Any]:
key = f"{owner}/{repo}"
if key in self.cache:
return self.cache[key]
# 1. Fetch recent commits from GitHub
commits = await self._fetch_recent_commits(owner, repo, limit=40)
if not commits:
return self._default_conventions()
commit_text = "\n".join([c.get("message", "")[:200] for c in commits[:30]])
# 2. Ask the LLM to infer conventions
prompt = f"""Analyze these recent commit messages and return ONLY a valid JSON object with these keys:
- "commit_style": either "conventional_commits" or "plain"
- "branch_naming_pattern": a short regex or example pattern (e.g. "feat/.*", "feature/*")
- "merge_policy": "squash", "merge", "rebase" or "unknown"
- "exampleGoodCommit": one good example from the history
Commit history:
{commit_text}
"""
try:
raw = llm_engine.generate(prompt, max_tokens=250)
conventions = self._parse_json(raw)
except Exception as e:
logger.warning(f"LLM convention inference failed: {e}")
conventions = self._default_conventions()
# Enrich
if commits:
conventions["exampleGoodCommit"] = commits[0].get("message", "")[:120]
conventions.setdefault("commit_style", "plain")
conventions.setdefault("branch_naming_pattern", "feature/*")
conventions.setdefault("merge_policy", "unknown")
self.cache[key] = conventions
return conventions
async def _fetch_recent_commits(self, owner: str, repo: str, limit: int = 30) -> list:
url = f"https://api.github.com/repos/{owner}/{repo}/commits"
params = {"per_page": min(limit, 100)}
headers = {"Accept": "application/vnd.github.v3+json"}
# Optional: add a GitHub token if you have one in env for higher rate limits
token = None # os.getenv("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"token {token}"
try:
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.get(url, params=params, headers=headers)
if resp.status_code != 200:
logger.warning(f"GitHub commits API {resp.status_code}")
return []
data = resp.json()
return [
{
"message": c.get("commit", {}).get("message", ""),
"sha": c.get("sha", ""),
"author": c.get("commit", {}).get("author", {}).get("name", ""),
}
for c in data
]
except Exception as e:
logger.error(f"Failed to fetch commits: {e}")
return []
def _parse_json(self, text: str) -> dict:
text = text.strip()
text = re.sub(r"```(?:json)?", "", text, flags=re.IGNORECASE).strip()
try:
return json.loads(text)
except json.JSONDecodeError:
start = text.find("{")
end = text.rfind("}")
if start != -1 and end > start:
try:
return json.loads(text[start : end + 1])
except Exception:
pass
return self._default_conventions()
def _default_conventions(self) -> dict:
return {
"commit_style": "plain",
"branch_naming_pattern": "feature/*",
"merge_policy": "unknown",
"exampleGoodCommit": "Update something",
}
# Singleton-style instance
convention_service = ConventionService()