| """ |
| Worker: README Rewriter |
| Uses Claude (or heuristics) to suggest targeted improvements to a project's README |
| based on the repository analysis results. |
| """ |
| from __future__ import annotations |
| import asyncio |
| import json |
| from typing import List, Optional |
|
|
| import httpx |
|
|
| from ..config import settings |
| from ..models import ReproResult |
| from ..llm import complete, strip_code_fences |
|
|
|
|
| async def _fetch_readme(github_url: str, timeout: int = 10) -> str: |
| """Fetch raw README content from GitHub.""" |
| |
| url = github_url.rstrip("/") |
| owner_repo = url.replace("https://github.com/", "") |
| for branch in ("main", "master"): |
| raw_url = f"https://raw.githubusercontent.com/{owner_repo}/{branch}/README.md" |
| try: |
| async with httpx.AsyncClient(timeout=timeout) as client: |
| resp = await client.get(raw_url) |
| if resp.status_code == 200: |
| return resp.text[:4000] |
| except Exception: |
| pass |
| return "" |
|
|
|
|
| async def _llm_suggest_readme_fixes( |
| readme_content: str, |
| repo_result: Optional[ReproResult], |
| github_url: str, |
| ) -> List[str]: |
| """Generate README suggestions via the shared LLM helper, with heuristic fallback.""" |
| try: |
| issues_summary = "" |
| if repo_result: |
| missing = [k for k, v in repo_result.files_present.items() if not v] |
| issues_summary = f"\nRepository issues detected: {', '.join(repo_result.issues[:5])}\nMissing files: {', '.join(missing[:5])}" |
|
|
| readme_excerpt = readme_content[:2000] if readme_content else "(No README found)" |
|
|
| prompt = f"""You are a technical writer helping an AI agent developer improve their README for the CROO Agent Store. |
| |
| Repository: {github_url}{issues_summary} |
| |
| Current README (truncated): |
| {readme_excerpt} |
| |
| Generate exactly 5 concrete, actionable suggestions to improve this README for: |
| 1. Agent marketplace buyers who need to quickly understand what the agent does and how to use it |
| 2. CROO hackathon judges evaluating technical execution and adoption readiness |
| 3. Other AI agents that may want to call this agent via CAP protocol |
| |
| Format as a JSON array of strings, each suggestion being 1-2 sentences. Focus on: |
| - Missing quickstart/installation instructions |
| - Unclear or missing input/output examples |
| - Missing pricing or SLA information for CAP buyers |
| - Missing environment variable documentation |
| - Poor explanation of the agent's unique value proposition |
| |
| Return ONLY a valid JSON array of 5 strings, no markdown, no explanation.""" |
|
|
| text = await complete(prompt, max_tokens=800, temperature=0) |
| if text is None: |
| return _heuristic_readme_suggestions(readme_content, repo_result) |
|
|
| suggestions = json.loads(strip_code_fences(text)) |
| return [str(s) for s in suggestions[:5]] |
|
|
| except Exception: |
| return _heuristic_readme_suggestions(readme_content, repo_result) |
|
|
|
|
| def _heuristic_readme_suggestions( |
| readme_content: str, |
| repo_result: Optional[ReproResult], |
| ) -> List[str]: |
| """Generate README suggestions without LLM.""" |
| suggestions = [] |
| content_lower = readme_content.lower() if readme_content else "" |
|
|
| if not readme_content: |
| return [ |
| "Add a README.md — this is the first thing judges and buyers will read.", |
| "Include a one-sentence description of what the agent does and what problem it solves.", |
| "Add a 'Quick Start' section with copy-paste installation commands.", |
| "Document all required environment variables with descriptions (no real values).", |
| "Add an example input and output to show what the agent produces.", |
| ] |
|
|
| if "quick start" not in content_lower and "quickstart" not in content_lower and "getting started" not in content_lower: |
| suggestions.append( |
| "Add a 'Quick Start' section with step-by-step commands so buyers can get running in under 5 minutes." |
| ) |
|
|
| if "example" not in content_lower and "sample" not in content_lower: |
| suggestions.append( |
| "Include a concrete input/output example so buyers understand exactly what to send and what they'll receive." |
| ) |
|
|
| if "price" not in content_lower and "cost" not in content_lower and "croo" not in content_lower: |
| suggestions.append( |
| "Add a 'Pricing' section documenting the cost per call and any SLA guarantees for CAP marketplace buyers." |
| ) |
|
|
| if ".env" not in content_lower and "environment" not in content_lower: |
| suggestions.append( |
| "Add an 'Environment Variables' section listing all required variables — ideally referencing .env.example." |
| ) |
|
|
| if repo_result and not repo_result.files_present.get("docker-compose.yml", False): |
| suggestions.append( |
| "Add docker-compose.yml and document Docker-based setup in the README for reproducible one-command startup." |
| ) |
|
|
| if len(suggestions) < 5: |
| suggestions.append( |
| "Add a badge section (CI status, version, license) at the top to signal project maturity to evaluators." |
| ) |
|
|
| if len(suggestions) < 5: |
| suggestions.append( |
| "Add a 'How It Works' section with a short architecture diagram or flowchart showing the agent's decision flow." |
| ) |
|
|
| return suggestions[:5] |
|
|
|
|
| async def suggest_readme_fixes( |
| github_url: str, |
| repo_result: Optional[ReproResult] = None, |
| ) -> List[str]: |
| """ |
| Analyse the project README and return up to 5 targeted improvement suggestions. |
| Uses Claude if ANTHROPIC_API_KEY is set, otherwise falls back to heuristics. |
| """ |
| readme_content = await _fetch_readme(github_url) |
| suggestions = await _llm_suggest_readme_fixes(readme_content, repo_result, github_url) |
| return suggestions |
|
|