diff --git a/codegen-examples/examples/enhanced-cicd-flow/.env.template b/codegen-examples/examples/enhanced-cicd-flow/.env.template new file mode 100644 index 000000000..bb8e9dbee --- /dev/null +++ b/codegen-examples/examples/enhanced-cicd-flow/.env.template @@ -0,0 +1,15 @@ +# Linear +LINEAR_ACCESS_TOKEN="your_token" +LINEAR_SIGNING_SECRET="your_secret" +LINEAR_TEAM_ID="your_team_id" + +# GitHub +GITHUB_TOKEN="your_github_token" + +# Slack +SLACK_SIGNING_SECRET="your_slack_secret" +SLACK_BOT_TOKEN="your_slack_token" + +# AI Providers +ANTHROPIC_API_KEY="your_anthropic_key" +OPENAI_API_KEY="your_openai_key" \ No newline at end of file diff --git a/codegen-examples/examples/enhanced-cicd-flow/README.md b/codegen-examples/examples/enhanced-cicd-flow/README.md new file mode 100644 index 000000000..4a6725284 --- /dev/null +++ b/codegen-examples/examples/enhanced-cicd-flow/README.md @@ -0,0 +1,73 @@ +# Enhanced CI/CD Flow with Codegen + +This example demonstrates a cohesive CI/CD workflow that integrates multiple Codegen components to create a seamless development experience from requirements to deployment. + +## Architecture Overview + +``` +[Requirements] → [Planning] → [Development] → [Review] → [Testing] → [Deployment] → [Monitoring] +``` + +### Components + +1. **Requirements & Planning Hub** (Linear + AI) + - Captures and analyzes requirements from Linear + - Breaks down complex tasks into manageable subtasks + - Creates a development plan with dependencies + +2. **AI-Assisted Development** (Local Checkout + Ticket-to-PR) + - Checks out code locally for development + - Uses AI to generate code changes based on requirements + - Creates PRs with detailed documentation + +3. **Comprehensive Code Review** (PR Review + Deep Analysis) + - Reviews PRs with multiple perspectives (style, security, performance) + - Performs deep code analysis to validate changes + - Provides feedback via GitHub and Slack + +4. **Continuous Knowledge & Assistance** (Slack Integration) + - Provides context and assistance throughout the pipeline + - Answers questions about the codebase and development process + - Facilitates team communication and knowledge sharing + +## Setup Instructions + +1. Clone this repository +2. Create a `.env` file with the required credentials (see `.env.template`) +3. Deploy the components using Modal + +```bash +# Deploy the components +modal deploy requirements_hub.py +modal deploy development_assistant.py +modal deploy code_review.py +modal deploy knowledge_assistant.py +``` + +## Usage + +1. Create a ticket in Linear with the "Codegen" label +2. The Requirements Hub will analyze the ticket and create a development plan +3. The Development Assistant will generate code changes and create a PR +4. The Code Review component will review the PR and provide feedback +5. The Knowledge Assistant will answer questions and provide context throughout the process + +## Environment Variables + +``` +# Linear +LINEAR_ACCESS_TOKEN="your_token" +LINEAR_SIGNING_SECRET="your_secret" +LINEAR_TEAM_ID="your_team_id" + +# GitHub +GITHUB_TOKEN="your_github_token" + +# Slack +SLACK_SIGNING_SECRET="your_slack_secret" +SLACK_BOT_TOKEN="your_slack_token" + +# AI Providers +ANTHROPIC_API_KEY="your_anthropic_key" +OPENAI_API_KEY="your_openai_key" +``` \ No newline at end of file diff --git a/codegen-examples/examples/enhanced-cicd-flow/code_review.py b/codegen-examples/examples/enhanced-cicd-flow/code_review.py new file mode 100644 index 000000000..ed24d241c --- /dev/null +++ b/codegen-examples/examples/enhanced-cicd-flow/code_review.py @@ -0,0 +1,280 @@ +"""Code Review component for the enhanced CI/CD flow. + +This component: +1. Reviews PRs with multiple perspectives (style, security, performance) +2. Performs deep code analysis to validate changes +3. Provides feedback via GitHub and Slack +""" + +import os +import logging +from typing import Dict, Any, List, Optional +from dataclasses import dataclass + +import modal +from fastapi import Request +from shared import ( + BASE_IMAGE, + create_app, + create_github_client, + create_codebase, + create_agent, + send_slack_message, + logger, +) +from codegen.extensions.github.types.events.pull_request import ( + PullRequestLabeledEvent, + PullRequestUnlabeledEvent, +) + +# Create app +app = create_app("code-review") + +# Define data structures +@dataclass +class ReviewResult: + """Represents the result of a code review.""" + pr_number: int + pr_title: str + pr_url: str + feedback: List[Dict[str, Any]] + summary: str + rating: str # "approve", "comment", or "request_changes" + +# AI prompts for code review +REVIEW_PROMPT = """ +You are an expert code reviewer. Your task is to review the following pull request: + +PR Title: {pr_title} +PR Description: {pr_description} + +Please review the code changes with a focus on: +1. Code quality and maintainability +2. Security vulnerabilities +3. Performance implications +4. Adherence to best practices +5. Test coverage + +For each issue you find, provide: +1. The file and line number +2. A description of the issue +3. A suggested fix + +Finally, provide an overall assessment of the PR and a recommendation (approve, comment, or request changes). +""" + +@app.cls(secrets=[modal.Secret.from_dotenv()], keep_warm=1) +class CodeReview: + """Handles GitHub webhook events and reviews PRs.""" + + @modal.enter() + def setup(self): + """Set up the code review component.""" + self.github_client = create_github_client() + logger.info("Code Review component initialized") + + @app.github.event("pull_request:labeled") + def handle_labeled(self, event: PullRequestLabeledEvent): + """Handle PR labeled events.""" + logger.info(f"[PULL_REQUEST:LABELED] PR #{event.number} labeled with: {event.label.name}") + + # Check if this is a Codegen PR + if event.label.name != "Codegen": + logger.info(f"Skipping PR #{event.number} (not labeled with 'Codegen')") + return + + # Notify Slack + send_slack_message( + app.slack.client, + "general", + f"🔍 Starting code review for PR #{event.number}: {event.pull_request.title}" + ) + + # Review the PR + review_result = self._review_pr(event) + + # Post review comments to GitHub + self._post_review_to_github(review_result) + + # Post review summary to Slack + self._post_review_to_slack(review_result) + + @app.github.event("pull_request:unlabeled") + def handle_unlabeled(self, event: PullRequestUnlabeledEvent): + """Handle PR unlabeled events.""" + logger.info(f"[PULL_REQUEST:UNLABELED] PR #{event.number} unlabeled: {event.label.name}") + + # Check if the Codegen label was removed + if event.label.name != "Codegen": + return + + # Remove bot comments + self._remove_bot_comments(event) + + @modal.web_endpoint(method="POST") + def entrypoint(self, event: Dict[str, Any], request: Request): + """Handle GitHub webhook events.""" + logger.info("[OUTER] Received GitHub webhook") + return app.github.handle(event, request) + + def _review_pr(self, event: PullRequestLabeledEvent) -> ReviewResult: + """Review a PR and return the results.""" + pr_number = event.number + pr_title = event.pull_request.title + pr_url = event.pull_request.html_url + + # Get PR details + pr = self.github_client.get_pr(pr_number) + + # Clone the repository + repo_name = pr.base.repo.full_name + codebase = create_codebase(repo_name, "python") + + # Checkout the PR branch + codebase.git.fetch("origin", f"pull/{pr_number}/head:pr-{pr_number}") + codebase.git.checkout(f"pr-{pr_number}") + + # Get the changed files + changed_files = self.github_client.get_pr_files(pr_number) + + # Perform deep code analysis + feedback = self._analyze_code_changes(codebase, changed_files) + + # Generate review summary + summary = self._generate_review_summary(feedback) + + # Determine review rating + rating = self._determine_review_rating(feedback) + + return ReviewResult( + pr_number=pr_number, + pr_title=pr_title, + pr_url=pr_url, + feedback=feedback, + summary=summary, + rating=rating, + ) + + def _analyze_code_changes(self, codebase, changed_files) -> List[Dict[str, Any]]: + """Analyze code changes and return feedback.""" + # In a real implementation, this would use more sophisticated analysis + # For this example, we'll create some sample feedback + feedback = [] + + for file in changed_files: + filepath = file.filename + + # Skip deleted files + if file.status == "removed": + continue + + # Read the file content + try: + content = codebase.get_file(filepath).content + except Exception as e: + logger.error(f"Error reading file {filepath}: {e}") + continue + + # Add some sample feedback + if filepath.endswith(".py"): + feedback.append({ + "file": filepath, + "line": 1, + "message": "Consider adding a docstring to explain the purpose of this file.", + "severity": "suggestion", + }) + + if "TODO" in content: + line_number = content.split("\n").index([line for line in content.split("\n") if "TODO" in line][0]) + 1 + feedback.append({ + "file": filepath, + "line": line_number, + "message": "TODO comments should be addressed before merging.", + "severity": "warning", + }) + + return feedback + + def _generate_review_summary(self, feedback: List[Dict[str, Any]]) -> str: + """Generate a summary of the review.""" + num_issues = len(feedback) + num_warnings = len([f for f in feedback if f["severity"] == "warning"]) + num_suggestions = len([f for f in feedback if f["severity"] == "suggestion"]) + + summary = f""" +# Code Review Summary + +I've reviewed the changes and found: +- {num_issues} total issues +- {num_warnings} warnings +- {num_suggestions} suggestions + +## Key Findings +""" + + if num_issues > 0: + for i, issue in enumerate(feedback[:3]): # Show top 3 issues + summary += f"{i+1}. **{issue['file']}** (line {issue['line']}): {issue['message']}\n" + + if num_issues > 3: + summary += f"... and {num_issues - 3} more issues\n" + else: + summary += "No issues found! The code looks great. 👍\n" + + return summary + + def _determine_review_rating(self, feedback: List[Dict[str, Any]]) -> str: + """Determine the review rating based on feedback.""" + num_warnings = len([f for f in feedback if f["severity"] == "warning"]) + + if num_warnings > 5: + return "request_changes" + elif num_warnings > 0: + return "comment" + else: + return "approve" + + def _post_review_to_github(self, review: ReviewResult): + """Post review comments to GitHub.""" + # Post individual comments + for issue in review.feedback: + self.github_client.create_pr_review_comment( + review.pr_number, + issue["file"], + issue["line"], + issue["message"], + ) + + # Post review summary + self.github_client.create_pr_review( + review.pr_number, + review.summary, + review.rating, + ) + + def _post_review_to_slack(self, review: ReviewResult): + """Post review summary to Slack.""" + emoji = "✅" if review.rating == "approve" else "⚠️" if review.rating == "comment" else "❌" + + message = f""" +{emoji} *Code Review Completed* +PR: <{review.pr_url}|#{review.pr_number} {review.pr_title}> + +{review.summary} +""" + + send_slack_message(app.slack.client, "general", message) + + def _remove_bot_comments(self, event: PullRequestUnlabeledEvent): + """Remove bot comments from a PR.""" + pr_number = event.number + + # Get all comments on the PR + comments = self.github_client.get_pr_comments(pr_number) + + # Filter for bot comments + bot_comments = [c for c in comments if c.user.login == "codegen-bot"] + + # Delete bot comments + for comment in bot_comments: + self.github_client.delete_pr_comment(comment.id) \ No newline at end of file diff --git a/codegen-examples/examples/enhanced-cicd-flow/development_assistant.py b/codegen-examples/examples/enhanced-cicd-flow/development_assistant.py new file mode 100644 index 000000000..412d44c51 --- /dev/null +++ b/codegen-examples/examples/enhanced-cicd-flow/development_assistant.py @@ -0,0 +1,207 @@ +"""Development Assistant for the enhanced CI/CD flow. + +This component: +1. Checks out code locally for development +2. Uses AI to generate code changes based on requirements +3. Creates PRs with detailed documentation +""" + +import os +import logging +from typing import Dict, Any, Optional +from dataclasses import dataclass + +import modal +from fastapi import Request +from shared import ( + BASE_IMAGE, + create_app, + create_linear_client, + create_github_client, + create_codebase, + create_agent, + format_linear_message, + has_codegen_label, + logger, +) + +# Create app +app = create_app("development-assistant") + +# Define data structures +@dataclass +class CodegenTask: + """Represents a task for the development assistant.""" + issue_id: str + issue_title: str + issue_description: str + issue_url: str + repository: str + branch: str + +# AI prompt for code generation +CODE_GENERATION_PROMPT = """ +You are an expert software developer. Your task is to implement the following requirement: + +{requirement} + +Repository: {repository} +Branch: {branch} + +Please analyze the codebase and make the necessary changes to implement this requirement. +Focus on: +1. Clean, maintainable code +2. Following the existing code style and patterns +3. Adding appropriate tests +4. Updating documentation as needed + +Provide a detailed explanation of your changes and the reasoning behind them. +""" + +@app.cls(secrets=[modal.Secret.from_dotenv()], keep_warm=1) +class DevelopmentAssistant: + """Handles Linear webhook events and generates code changes.""" + + @modal.enter() + def setup(self): + """Set up the development assistant.""" + # Subscribe to Linear webhooks + app.linear.subscribe_all_handlers() + self.linear_client = create_linear_client() + self.github_client = create_github_client() + logger.info("Development Assistant initialized") + + @modal.exit() + def cleanup(self): + """Clean up resources.""" + app.linear.unsubscribe_all_handlers() + + @modal.web_endpoint(method="POST") + @app.linear.event("Issue", should_handle=has_codegen_label) + def handle_implementation_task(self, data: Dict[str, Any], request: Request): + """Handle implementation tasks from Linear.""" + logger.info(f"Received Linear issue event: {data.get('action', 'unknown')}") + + # Extract issue details + issue_id = data.get("data", {}).get("id") + issue_title = data.get("data", {}).get("title", "") + issue_description = data.get("data", {}).get("description", "") + issue_url = data.get("url", "") + + if not issue_id: + logger.error("Missing issue ID in webhook data") + return {"status": "error", "message": "Missing issue ID"} + + # Check if this is an implementation task + if not self._is_implementation_task(issue_title, issue_description): + logger.info(f"Skipping non-implementation task: {issue_title}") + return {"status": "skipped", "message": "Not an implementation task"} + + # Acknowledge receipt + self.linear_client.comment_on_issue( + issue_id, + "I'm working on implementing this task. I'll create a PR with the changes shortly. 🛠️" + ) + + # Extract repository and branch information + repository, branch = self._extract_repo_info(issue_description) + + # Create a task object + task = CodegenTask( + issue_id=issue_id, + issue_title=issue_title, + issue_description=issue_description, + issue_url=issue_url, + repository=repository, + branch=branch, + ) + + # Generate code changes and create PR + pr_url = self._implement_task(task) + + # Update the issue with the PR link + self.linear_client.comment_on_issue( + issue_id, + f"✅ Implementation complete! Please review the PR: {pr_url}" + ) + + return {"status": "success", "message": "Implementation complete", "pr_url": pr_url} + + def _is_implementation_task(self, title: str, description: str) -> bool: + """Check if this is an implementation task.""" + # In a real implementation, this would use more sophisticated logic + # For this example, we'll check for keywords in the title or description + implementation_keywords = ["implement", "code", "develop", "build", "create"] + + title_lower = title.lower() + description_lower = description.lower() + + for keyword in implementation_keywords: + if keyword in title_lower or keyword in description_lower: + return True + + return False + + def _extract_repo_info(self, description: str) -> tuple[str, str]: + """Extract repository and branch information from the description.""" + # In a real implementation, this would parse the description to find repo info + # For this example, we'll use default values + repository = "codegen-sh/codegen" + branch = "feature/auto-generated" + + # Look for repository and branch information in the description + lines = description.split("\n") + for line in lines: + if line.startswith("Repository:"): + repository = line.split(":", 1)[1].strip() + elif line.startswith("Branch:"): + branch = line.split(":", 1)[1].strip() + + return repository, branch + + def _implement_task(self, task: CodegenTask) -> str: + """Implement the task and create a PR.""" + # Initialize codebase + codebase = create_codebase(task.repository, "python") + + # Create a new branch + branch_name = f"{task.branch}-{task.issue_id[:8]}" + codebase.git.checkout("-b", branch_name) + + # Format the task for the agent + query = format_linear_message(task.issue_title, task.issue_description) + + # Create an agent to implement the changes + agent = create_agent(codebase) + + # Run the agent to implement the changes + agent.run(query) + + # Create a PR with the changes + pr_title = f"[{task.issue_id}] {task.issue_title}" + pr_body = f""" +# {task.issue_title} + +This PR implements the changes requested in Linear issue: {task.issue_url} + +## Changes +- Implemented the requested functionality +- Added tests +- Updated documentation + +## Related Issues +- Linear: {task.issue_url} +""" + + # Create the PR + pr = codebase.github.create_pr( + title=pr_title, + body=pr_body, + base="main", + head=branch_name, + ) + + # Reset the codebase for the next task + codebase.reset() + + return pr.html_url \ No newline at end of file diff --git a/codegen-examples/examples/enhanced-cicd-flow/knowledge_assistant.py b/codegen-examples/examples/enhanced-cicd-flow/knowledge_assistant.py new file mode 100644 index 000000000..3e4dada7c --- /dev/null +++ b/codegen-examples/examples/enhanced-cicd-flow/knowledge_assistant.py @@ -0,0 +1,191 @@ +"""Knowledge Assistant for the enhanced CI/CD flow. + +This component: +1. Provides context and assistance throughout the pipeline +2. Answers questions about the codebase and development process +3. Facilitates team communication and knowledge sharing +""" + +import os +import logging +from typing import Dict, Any, Optional + +import modal +from fastapi import FastAPI, Request +from slack_bolt import App +from slack_bolt.adapter.fastapi import SlackRequestHandler +from shared import ( + BASE_IMAGE, + create_app, + create_codebase, + logger, +) +from codegen.extensions import VectorIndex +from openai import OpenAI + +# Create app +app = modal.App("knowledge-assistant") + +# AI prompts for knowledge assistance +KNOWLEDGE_PROMPT = """ +You are an expert software developer and knowledge assistant. Your task is to answer the following question about the codebase: + +Question: {question} + +Context: +{context} + +Please provide a clear, concise answer based on the context provided. If you don't know the answer, say so and suggest where the user might find more information. +""" + +@app.function( + image=BASE_IMAGE, + secrets=[modal.Secret.from_dotenv()], + timeout=3600, +) +@modal.asgi_app() +def fastapi_app(): + """Create FastAPI app with Slack handlers.""" + # Initialize Slack app with secrets from environment + slack_app = App( + token=os.environ["SLACK_BOT_TOKEN"], + signing_secret=os.environ["SLACK_SIGNING_SECRET"], + ) + + # Create FastAPI app + web_app = FastAPI() + handler = SlackRequestHandler(slack_app) + + # Store responded messages to avoid duplicates + responded = {} + + # Initialize codebase and vector index + codebase = None + index = None + + def get_codebase(): + """Get or initialize the codebase.""" + nonlocal codebase + if codebase is None: + codebase = create_codebase("codegen-sh/codegen", "python") + return codebase + + def get_index(): + """Get or initialize the vector index.""" + nonlocal index, codebase + if index is None: + codebase = get_codebase() + index = VectorIndex(codebase) + + # Try to load existing index or create new one + index_path = "/root/codegen_index.pkl" + try: + index.load(index_path) + except FileNotFoundError: + # Create new index if none exists + index.create() + index.save(index_path) + + return index + + def format_response(answer: str, context: list[tuple[str, int]]) -> str: + """Format the response for Slack with file links.""" + response = f"*Answer:*\n{answer}\n\n*Relevant Files:*\n" + for filename, score in context: + if "#chunk" in filename: + filename = filename.split("#chunk")[0] + github_link = f"https://github.com/codegen-sh/codegen/blob/develop/{filename}" + response += f"• <{github_link}|{filename}>\n" + return response + + def answer_question(query: str) -> tuple[str, list[tuple[str, int]]]: + """Use RAG to answer a question about the codebase.""" + # Get vector index + index = get_index() + codebase = get_codebase() + + # Find relevant files + results = index.similarity_search(query, k=5) + + # Collect context from relevant files + context = "" + for filepath, score in results: + if "#chunk" in filepath: + filepath = filepath.split("#chunk")[0] + file = codebase.get_file(filepath) + context += f"File: {file.filepath}\n```\n{file.content}\n```\n\n" + + # Create prompt for OpenAI + prompt = f"""You are an expert on the codegen codebase. Given the following code context and question, provide a clear and accurate answer. +Focus on the specific code shown in the context and implementation details. + +Note that your response will be rendered in Slack, so make sure to use Slack markdown. Keep it short + sweet, like 2 paragraphs + some code blocks max. + +Question: {query} + +Relevant code: +{context} + +Answer:""" + + client = OpenAI() + response = client.chat.completions.create( + model="gpt-4o", + messages=[ + {"role": "system", "content": "You are a code expert. Answer questions about the given repo based on RAG'd results."}, + {"role": "user", "content": prompt}, + ], + temperature=0, + ) + + return response.choices[0].message.content, results + + @slack_app.event("app_mention") + def handle_mention(event: Dict[str, Any], say: Any) -> None: + """Handle mentions of the bot in channels.""" + logger.info(f"Received Slack mention: {event}") + + # Skip if we've already answered this question + if event["ts"] in responded: + return + responded[event["ts"]] = True + + # Get message text without the bot mention + query = event["text"].split(">", 1)[1].strip() + if not query: + say("Please ask a question about the codebase or development process!") + return + + try: + # Add typing indicator emoji + slack_app.client.reactions_add( + channel=event["channel"], + timestamp=event["ts"], + name="writing_hand", + ) + + # Get answer using RAG + answer, context = answer_question(query) + + # Format and send response in thread + response = format_response(answer, context) + say(text=response, thread_ts=event["ts"]) + + except Exception as e: + # Send error message in thread + say(text=f"Error: {str(e)}", thread_ts=event["ts"]) + + @web_app.post("/slack/events") + async def endpoint(request: Request): + """Handle Slack events and verify requests.""" + return await handler.handle(request) + + @web_app.post("/slack/verify") + async def verify(request: Request): + """Handle Slack URL verification challenge.""" + data = await request.json() + if data["type"] == "url_verification": + return {"challenge": data["challenge"]} + return await handler.handle(request) + + return web_app \ No newline at end of file diff --git a/codegen-examples/examples/enhanced-cicd-flow/requirements_hub.py b/codegen-examples/examples/enhanced-cicd-flow/requirements_hub.py new file mode 100644 index 000000000..aa04ac548 --- /dev/null +++ b/codegen-examples/examples/enhanced-cicd-flow/requirements_hub.py @@ -0,0 +1,212 @@ +"""Requirements & Planning Hub for the enhanced CI/CD flow. + +This component: +1. Captures and analyzes requirements from Linear +2. Breaks down complex tasks into manageable subtasks +3. Creates a development plan with dependencies +""" + +import os +import logging +from typing import Dict, Any, List, Optional +from dataclasses import dataclass + +import modal +from fastapi import Request +from shared import ( + BASE_IMAGE, + create_app, + create_linear_client, + has_codegen_label, + logger, +) + +# Create app +app = create_app("requirements-hub") + +# Define data structures +@dataclass +class Task: + """Represents a development task.""" + title: str + description: str + priority: str + dependencies: List[str] = None + + def __post_init__(self): + if self.dependencies is None: + self.dependencies = [] + +@dataclass +class DevelopmentPlan: + """Represents a development plan with tasks.""" + title: str + description: str + tasks: List[Task] + repository: str + branch: str + +# AI prompt for task breakdown +TASK_BREAKDOWN_PROMPT = """ +You are an expert software architect and project planner. Your task is to analyze the following requirement and break it down into smaller, manageable tasks. + +For each task: +1. Provide a clear title +2. Write a detailed description +3. Assign a priority (High, Medium, Low) +4. Identify dependencies between tasks + +Requirement: +{requirement} + +Please format your response as a structured plan with clear tasks and dependencies. +""" + +@app.cls(secrets=[modal.Secret.from_dotenv()], keep_warm=1) +class RequirementsHub: + """Handles Linear webhook events and processes requirements.""" + + @modal.enter() + def setup(self): + """Set up the requirements hub.""" + # Subscribe to Linear webhooks + app.linear.subscribe_all_handlers() + self.linear_client = create_linear_client() + logger.info("Requirements Hub initialized") + + @modal.exit() + def cleanup(self): + """Clean up resources.""" + app.linear.unsubscribe_all_handlers() + + @modal.web_endpoint(method="POST") + @app.linear.event("Issue", should_handle=has_codegen_label) + def handle_issue(self, data: Dict[str, Any], request: Request): + """Handle incoming Linear issue events.""" + logger.info(f"Received Linear issue event: {data.get('action', 'unknown')}") + + # Extract issue details + issue_id = data.get("data", {}).get("id") + issue_title = data.get("data", {}).get("title", "") + issue_description = data.get("data", {}).get("description", "") + issue_url = data.get("url", "") + + if not issue_id: + logger.error("Missing issue ID in webhook data") + return {"status": "error", "message": "Missing issue ID"} + + # Acknowledge receipt + self.linear_client.comment_on_issue( + issue_id, + "I've received your request and am analyzing it to create a development plan. 🔍" + ) + + # Analyze the requirement and break it down into tasks + development_plan = self._analyze_requirement(issue_title, issue_description) + + # Create child issues for each task + self._create_child_issues(issue_id, development_plan) + + # Update the parent issue with the development plan + self._update_parent_issue(issue_id, development_plan) + + return {"status": "success", "message": "Development plan created"} + + def _analyze_requirement(self, title: str, description: str) -> DevelopmentPlan: + """Analyze a requirement and break it down into tasks.""" + # In a real implementation, this would use an AI model to analyze the requirement + # For this example, we'll create a simple plan with predefined tasks + + # Extract repository and branch from description (if available) + repository = "codegen-sh/codegen" # Default repository + branch = f"feature/{title.lower().replace(' ', '-')}" + + # Create a development plan with tasks + tasks = [ + Task( + title="Task 1: Analysis and Design", + description="Analyze the requirements and create a detailed design document.", + priority="High", + ), + Task( + title="Task 2: Implementation", + description="Implement the core functionality based on the design.", + priority="High", + dependencies=["Task 1: Analysis and Design"], + ), + Task( + title="Task 3: Testing", + description="Create and run tests to verify the implementation.", + priority="Medium", + dependencies=["Task 2: Implementation"], + ), + Task( + title="Task 4: Documentation", + description="Update documentation to reflect the changes.", + priority="Low", + dependencies=["Task 2: Implementation"], + ), + ] + + return DevelopmentPlan( + title=title, + description=description, + tasks=tasks, + repository=repository, + branch=branch, + ) + + def _create_child_issues(self, parent_id: str, plan: DevelopmentPlan): + """Create child issues for each task in the development plan.""" + for task in plan.tasks: + # Create a child issue for the task + child_issue = self.linear_client.create_issue( + title=task.title, + description=task.description, + parent_id=parent_id, + priority=task.priority, + ) + + # Add a comment with dependency information if applicable + if task.dependencies: + dependencies_text = "\n".join([f"- {dep}" for dep in task.dependencies]) + self.linear_client.comment_on_issue( + child_issue.id, + f"This task depends on:\n{dependencies_text}" + ) + + def _update_parent_issue(self, issue_id: str, plan: DevelopmentPlan): + """Update the parent issue with the development plan.""" + # Format the development plan as markdown + tasks_text = "\n".join([ + f"- **{task.title}** ({task.priority})\n {task.description}" + for task in plan.tasks + ]) + + plan_text = f""" +# Development Plan + +## Overview +{plan.description} + +## Repository +{plan.repository} + +## Branch +{plan.branch} + +## Tasks +{tasks_text} + +## Next Steps +The tasks have been created as child issues. Please assign them to team members and track progress here. +""" + + # Update the parent issue with the development plan + self.linear_client.comment_on_issue(issue_id, plan_text) + + # Notify that the plan is ready + self.linear_client.comment_on_issue( + issue_id, + "✅ Development plan created! I've broken down the requirement into manageable tasks and created child issues for each one." + ) \ No newline at end of file diff --git a/codegen-examples/examples/enhanced-cicd-flow/shared.py b/codegen-examples/examples/enhanced-cicd-flow/shared.py new file mode 100644 index 000000000..04fd2b95b --- /dev/null +++ b/codegen-examples/examples/enhanced-cicd-flow/shared.py @@ -0,0 +1,122 @@ +"""Shared utilities and configurations for the enhanced CI/CD flow.""" + +import os +import logging +from typing import Any, Dict, List, Optional + +import modal +from codegen.extensions.events.codegen_app import CodegenApp +from codegen.extensions.clients.linear import LinearClient +from codegen.extensions.clients.github import GitHubClient +from codegen import Codebase, CodeAgent + +# Configure logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + +# Create base image with all dependencies +BASE_IMAGE = ( + modal.Image.debian_slim(python_version="3.13") + .apt_install("git") + .pip_install( + "fastapi[standard]", + "codegen>=0.26.3", + "slack_sdk", + "openai>=1.1.0", + "anthropic>=0.5.0", + ) +) + +# Create shared event bus for component communication +class EventBus: + """Simple event bus for communication between components.""" + + def __init__(self): + self.subscribers = {} + + def subscribe(self, event_type: str, callback): + """Subscribe to an event type.""" + if event_type not in self.subscribers: + self.subscribers[event_type] = [] + self.subscribers[event_type].append(callback) + + def publish(self, event_type: str, data: Any): + """Publish an event to all subscribers.""" + if event_type in self.subscribers: + for callback in self.subscribers[event_type]: + callback(data) + +# Create shared state management +class SharedState: + """Shared state management for the CI/CD flow.""" + + def __init__(self): + self.state = {} + + def set(self, key: str, value: Any): + """Set a value in the shared state.""" + self.state[key] = value + + def get(self, key: str, default: Any = None) -> Any: + """Get a value from the shared state.""" + return self.state.get(key, default) + + def delete(self, key: str): + """Delete a value from the shared state.""" + if key in self.state: + del self.state[key] + +# Helper functions for Linear integration +def has_codegen_label(data: Dict[str, Any]) -> bool: + """Check if a Linear issue has the 'Codegen' label.""" + if "labels" not in data or not data["labels"]: + return False + + for label in data["labels"]: + if label["name"].lower() == "codegen": + return True + + return False + +def format_linear_message(title: str, description: str) -> str: + """Format a Linear issue title and description for the CodeAgent.""" + return f""" +# {title} + +{description} + +Please implement the changes described above. +""" + +# Helper functions for GitHub integration +def create_codebase(repo: str, language: str) -> Codebase: + """Create a codebase from a GitHub repository.""" + return Codebase.from_repo(repo, language=language) + +# Helper functions for Slack integration +def send_slack_message(client, channel: str, message: str, thread_ts: Optional[str] = None): + """Send a message to a Slack channel.""" + client.chat_postMessage( + channel=channel, + text=message, + thread_ts=thread_ts, + ) + +# Create shared CodegenApp instance +def create_app(name: str) -> CodegenApp: + """Create a CodegenApp instance with the shared image.""" + return CodegenApp(name=name) + +# Create shared clients +def create_linear_client() -> LinearClient: + """Create a Linear client with the access token from environment.""" + return LinearClient(access_token=os.environ["LINEAR_ACCESS_TOKEN"]) + +def create_github_client() -> GitHubClient: + """Create a GitHub client with the token from environment.""" + return GitHubClient(token=os.environ["GITHUB_TOKEN"]) + +# Create shared agent +def create_agent(codebase: Codebase) -> CodeAgent: + """Create a CodeAgent for the given codebase.""" + return CodeAgent(codebase) \ No newline at end of file