18 בניית מיומנויות

Build -- Code Review & DevOps Agent

בואו נדבר בגובה העיניים: code review הוא צוואר בקבוק. PR פתוח ביום שני, reviewer עסוק, ביום רביעי מישהו מסתכל, ביום חמישי יש merge conflicts. בינתיים ה-developer עבר ל-3 features אחרים ושכח מה הוא כתב. ובדיקות security? "נעשה את זה לפני release." ו-monitoring? "נגדיר כשיהיה incident." בפרק הזה תבנו DevOps Agent מלא: סוכן שקורא PR diffs, מזהה bugs ו-security issues, מייצר tests אוטומטית, עוזר ב-deployments, ומנטר production. לא demo -- סוכן שבאמת עובד, עם GitHub MCP, Claude Agent SDK, webhook handling, ו-CI/CD integration. זה הפרויקט הכי "developer-facing" בקורס, וכל שורת קוד שתכתבו כאן תשפר את ה-workflow של הצוות שלכם מהיום הראשון.

מה יהיה לך בסוף הפרק הזה
מה תוכלו לעשות אחרי הפרק הזה
לפני שמתחילים
הפרויקט שלך -- קו אדום לאורך הקורס

בפרק 17 בניתם Marketing Agent שמנתח קמפיינים, מייצר תוכן, ומנהל campaigns. עכשיו אתם עוברים ל-developer territory: DevOps Agent שמאיץ את תהליכי הפיתוח עצמם. הפרויקט הזה משלב כל מה שלמדתם -- multi-agent orchestration מפרק 13, guardrails מפרק 14, tool use מפרק 11, MCP מפרק 3. בפרק 19 תיקחו את כל מה שבניתם עד כה ותעבירו אותו לפרודקשן אמיתי -- Deploy to Production.

מילון מונחים -- פרק 18
מונח (English)עבריתהסבר
Pull Request (PR)בקשת משיכהבקשה לשלב שינויים מ-branch אחד לאחר. נקודת הכניסה המרכזית של ה-review agent
Diffהבדליםההבדלים בין שתי גרסאות קוד. קווים ירוקים (הוספות) ואדומים (מחיקות)
Code Reviewסקירת קודתהליך שבו מפתח (או סוכן) בודק קוד של מפתח אחר לפני שהוא נכנס ל-codebase
CI/CDאינטגרציה ופריסה רציפיםContinuous Integration / Continuous Deployment -- אוטומציה של build, test, ו-deploy
GitHub Actions--מערכת CI/CD מובנית ב-GitHub. Workflows שרצים על events כמו PR opened, push, schedule
Webhook--HTTP callback שנשלח כש-event מתרחש. GitHub שולח webhook כשנפתח PR, נעשה push, וכו'
OWASP Top 10--רשימת 10 הפגיעויות הנפוצות ביותר ב-web applications. תקן התעשייה לsecurity
Secret Detectionזיהוי סודותסריקת קוד לזיהוי API keys, passwords, tokens שנכנסו בטעות ל-repository
Dependency Scanningסריקת תלויותבדיקת ספריות צד-שלישי (npm, pip) עבור פגיעויות ידועות (CVEs)
Rollbackחזרה לגרסה קודמתחזרה לגרסה יציבה כשdeployment חדש גורם לבעיות. חייב להיות מהיר ואוטומטי
Runbookספר הפעלהמדריך צעד-אחרי-צעד לטיפול ב-incident ידוע. הסוכן יכול לבצע runbooks אוטומטית
SLA (Service Level Agreement)הסכם רמת שירותהתחייבות על uptime, latency, response time. ה-monitoring agent עוקב אחרי עמידה ב-SLA
MCP (Model Context Protocol)--פרוטוקול סטנדרטי לחיבור LLMs ל-tools חיצוניים. ה-DevOps agent משתמש ב-GitHub MCP server
Anomaly Detectionזיהוי חריגותזיהוי אוטומטי של דפוסים חריגים -- error rate שקופץ, latency שעולה, traffic pattern משונה
False Positiveהתראת שוואכשהסוכן מדווח על בעיה שלא באמת קיימת. יותר מדי false positives = המפתחים מתעלמים
Inline Commentהערה בשורההערת review שמצביעה על שורה ספציפית בקוד. הדרך הכי אפקטיבית לתת feedback ב-PR

ארכיטקטורה ותכנון המערכת

intermediate30 דקותconcept + practice

לפני שכותבים שורה אחת של קוד, צריך להבין מה אנחנו בונים ולמה. ה-DevOps Agent הוא לא סוכן אחד -- הוא מערכת של סוכנים מתמחים שעובדים יחד תחת orchestrator אחד.

הבעיה שאנחנו פותרים

בעיההמצב היוםהמצב עם DevOps Agent
Code review ממתיןPR פתוח 1-3 ימים עד שreviewer מגיעReview ראשוני תוך 2 דקות מפתיחת PR
Security blind spotsSecurity review רק לפני release, אם בכללסריקת security אוטומטית על כל PR
Test coverage gaps"נכתוב tests אחר כך" (spoiler: לא נכתוב)Tests מוצעים אוטומטית לקוד חדש
Deployment anxiety"מי deploy-ם ביום שישי?" -- אף אחדPre-deploy checks, monitoring, auto-rollback
Incident responseAlert ב-3 בלילה, מי שבתורן מנסה להבין מה קרהAgent מנתח logs, מזהה root cause, מציע פתרון

Architecture: Router + Specialist Agents

הארכיטקטורה מבוססת על ה-Router Pattern מפרק 13 (Multi-Agent). event מגיע מ-GitHub, ה-router מזהה את סוג האירוע, ומפנה לסוכן המתמחה:

# Architecture Overview -- DevOps Agent System
#
# GitHub Event (webhook)
#     |
#     v
# +---------------------------+
# |     Event Router           |
# |  (classify and dispatch)   |
# +----------+----------------+
#            |
#     +------+----------+--------------+
#     v      v          v              v
# +-------+ +--------+ +-----------+ +------------+
# |Review | |Security| |Test Gen   | |Deploy      |
# |Agent  | |Scanner | |Agent      | |Assistant   |
# +-------+ +--------+ +-----------+ +------------+
#     |         |           |              |
#     v         v           v              v
# +---------------------------------------------+
# |          GitHub API (comments, checks)       |
# |          Slack (notifications)               |
# |          Metrics API (monitoring)            |
# +---------------------------------------------+

Tech Stack

רכיבטכנולוגיהלמה
LLMClaude Sonnet 4.6יחס מחיר/איכות מצוין, 1M context לPRs גדולים
Agent SDKClaude Agent SDK (Python/TS)tool calling מובנה, streaming, guardrails
GitHub IntegrationGitHub MCP Server + REST APIMCP לקריאת repos, REST API לכתיבת comments ו-checks
CI/CDGitHub Actionsnative integration, event-driven
NotificationsSlack APIהודעות לצוות על תוצאות review ו-deploy status
MonitoringDatadog / Grafana APIמטריקות, logs, alerting
StateSQLite / Redisהיסטוריית reviews, learning, configuration
עשו עכשיו 5 דקות

פתחו repo חדש ב-GitHub (או השתמשו באחד קיים). צרו PR קטן עם 2-3 שינויים -- פונקציה חדשה, fix קטן, ושינוי ב-config. ה-PR הזה ישמש אתכם כ-test case לאורך כל הפרק.

הגדרת ה-Event Router

ה-Router הוא הנקודה המרכזית. הוא מקבל GitHub webhook events ומפנה לסוכן המתאים:

# Python - Event Router
import anthropic
from dataclasses import dataclass
from enum import Enum
from typing import Optional

class EventType(Enum):
    PR_OPENED = "pull_request.opened"
    PR_UPDATED = "pull_request.synchronize"
    PR_REVIEW_REQUESTED = "pull_request.review_requested"
    DEPLOY_TRIGGERED = "deployment"
    ALERT_FIRED = "alert"
    COMMENT_CREATED = "issue_comment.created"

@dataclass
class GitHubEvent:
    event_type: EventType
    repo: str
    payload: dict
    pr_number: Optional[int] = None

class DevOpsRouter:
    """Event Router -- routes GitHub events to specialist agents."""

    def __init__(self):
        self.client = anthropic.Anthropic()
        self.review_agent = CodeReviewAgent()
        self.security_agent = SecurityAgent()
        self.test_agent = TestGenerationAgent()
        self.deploy_agent = DeployAssistant()
        self.monitor_agent = MonitorAgent()

    async def route(self, event: GitHubEvent) -> dict:
        """Route event to the appropriate agent."""
        routing_table = {
            EventType.PR_OPENED: self._handle_pr,
            EventType.PR_UPDATED: self._handle_pr,
            EventType.PR_REVIEW_REQUESTED: self._handle_pr,
            EventType.DEPLOY_TRIGGERED: self._handle_deploy,
            EventType.ALERT_FIRED: self._handle_alert,
            EventType.COMMENT_CREATED: self._handle_comment,
        }
        handler = routing_table.get(event.event_type)
        if not handler:
            return {"status": "unknown_event", "event": event.event_type}
        return await handler(event)

    async def _handle_pr(self, event: GitHubEvent) -> dict:
        """PR event -- run review, security, and test generation in parallel."""
        import asyncio
        review_task = asyncio.create_task(self.review_agent.review(event.repo, event.pr_number))
        security_task = asyncio.create_task(self.security_agent.scan(event.repo, event.pr_number))
        test_task = asyncio.create_task(self.test_agent.generate(event.repo, event.pr_number))
        return {
            "review": await review_task,
            "security": await security_task,
            "tests": await test_task,
        }

    async def _handle_deploy(self, event: GitHubEvent) -> dict:
        return await self.deploy_agent.handle_deploy(event)

    async def _handle_alert(self, event: GitHubEvent) -> dict:
        return await self.monitor_agent.analyze_alert(event)

    async def _handle_comment(self, event: GitHubEvent) -> dict:
        comment = event.payload.get("comment", {}).get("body", "")
        if "@devops-agent" in comment.lower():
            return await self.review_agent.respond_to_comment(event.repo, event.pr_number, comment)
        return {"status": "ignored", "reason": "not addressed to bot"}
Framework: The DevOps Agent Routing Matrix

לכל event מ-GitHub, הרצו את הסוכנים הנכונים:

GitHub EventReview AgentSecurity ScannerTest GenDeploy AgentMonitor
PR OpenedVVV----
PR Updated (push)V (incremental)VV (new files)----
PR Comment @botV (respond)on requeston request----
PR Merged------V (pre-deploy)--
Deploy Started------V (monitor)V (baseline)
Alert Fired------V (rollback?)V (diagnose)

עיקרון מפתח: PR events מפעילים review + security + tests במקביל. Deploy events מפעילים deploy assistant + monitor ברצף.

Code Review Agent -- סקירת קוד אוטומטית

advanced45 דקותpractice

ה-Code Review Agent הוא הלב של המערכת. הוא קורא את ה-diff של PR, מבין את ההקשר, ומייצר review comments שהם ספציפיים, actionable, וקונסטרוקטיביים.

עקרונות Review טוב

עיקרוןדוגמה רעהדוגמה טובה
ספציפי"הקוד הזה לא טוב""בשורה 42, users.find() בלי index יגרום ל-full table scan על 100K+ רשומות"
Actionable"צריך לשפר את ה-error handling""הוסיפו try/catch סביב ה-API call בשורה 15. טפלו ב-timeout (408) ו-rate limit (429) בנפרד"
קונסטרוקטיבי"למה עשית את זה ככה?""גישה אלטרנטיבית: השתמשו ב-Map במקום Object כדי לקבל O(1) lookup"
Prioritized50 הערות על כל דבר קטן3-5 הערות מהותיות עם severity (critical / suggestion / nit)

קטגוריות Review

קטגוריהמה בודקיםSeverity ברירת מחדל
Correctnessבאגים, לוגיקה שגויה, race conditions, null dereferencesCritical / High
SecuritySQL injection, XSS, secrets, permissionsCritical
PerformanceN+1 queries, missing indexes, unnecessary allocationsMedium
Readabilitynaming, complexity, comments, code organizationLow / Nit
Test Coverageחסרים tests, edge cases לא מכוסיםMedium

מימוש ב-Python עם Claude Agent SDK

# Python - Code Review Agent (Full Implementation)
import anthropic
import json
import re
from dataclasses import dataclass, field
import httpx

@dataclass
class ReviewComment:
    file: str
    line: int
    body: str
    severity: str  # "critical", "high", "medium", "low", "nit"
    category: str  # "correctness", "security", "performance", "readability", "testing"

@dataclass
class ReviewResult:
    summary: str
    comments: list[ReviewComment] = field(default_factory=list)
    approved: bool = False
    score: int = 0  # 0-100

class CodeReviewAgent:
    """Code Review Agent -- analyzes PRs and generates review comments."""

    SYSTEM_PROMPT = """You are an expert code reviewer. Review PR diffs and provide
constructive, specific, actionable feedback.

## Categories: correctness, security, performance, readability, test coverage
## Rules:
- Be SPECIFIC: reference exact file names and line numbers
- Be ACTIONABLE: suggest a concrete fix
- PRIORITIZE: critical issues first. Max 5-7 comments
- Use severity: critical, high, medium, low, nit

## Output: JSON with summary, approved (bool), score (0-100), comments array"""

    def __init__(self, github_token: str = None):
        self.client = anthropic.Anthropic()
        self.http = httpx.AsyncClient(
            headers={"Authorization": f"token {github_token}"} if github_token else {}
        )

    async def review(self, repo: str, pr_number: int) -> ReviewResult:
        """Full PR review."""
        pr_data = await self._fetch_pr_data(repo, pr_number)
        diff = await self._fetch_pr_diff(repo, pr_number)
        files = await self._fetch_changed_files(repo, pr_number)
        context = await self._fetch_repo_context(repo)
        review = await self._analyze_with_claude(pr_data, diff, files, context)
        await self._post_review(repo, pr_number, review)
        return review

    async def _fetch_pr_diff(self, repo: str, pr_number: int) -> str:
        resp = await self.http.get(
            f"https://api.github.com/repos/{repo}/pulls/{pr_number}",
            headers={"Accept": "application/vnd.github.v3.diff"}
        )
        return resp.text

    async def _fetch_repo_context(self, repo: str) -> str:
        context_parts = []
        for fname in ["README.md", "CONTRIBUTING.md", ".eslintrc.json", "pyproject.toml"]:
            try:
                resp = await self.http.get(
                    f"https://api.github.com/repos/{repo}/contents/{fname}",
                    headers={"Accept": "application/vnd.github.v3.raw"})
                if resp.status_code == 200:
                    context_parts.append(f"=== {fname} ===\n{resp.text[:2000]}")
            except Exception:
                pass
        return "\n\n".join(context_parts)

    async def _analyze_with_claude(self, pr_data, diff, files, context) -> ReviewResult:
        user_message = f"""Review this Pull Request.
## PR: {pr_data.get('title', 'N/A')} by {pr_data.get('user', {}).get('login', 'N/A')}
Files changed: {len(files)} | +{pr_data.get('additions', 0)} / -{pr_data.get('deletions', 0)}
## Context\n{context[:3000]}
## Diff\n{diff[:50000]}"""

        response = self.client.messages.create(
            model="claude-sonnet-4-6-20260325", max_tokens=4096,
            system=self.SYSTEM_PROMPT,
            messages=[{"role": "user", "content": user_message}])

        text = response.content[0].text
        try:
            data = json.loads(text)
        except json.JSONDecodeError:
            m = re.search(r'```json?\s*\n(.*?)\n```', text, re.DOTALL)
            data = json.loads(m.group(1)) if m else {"summary": "Parse failed", "comments": []}

        return ReviewResult(
            summary=data.get("summary", ""), score=data.get("score", 0),
            approved=data.get("approved", False),
            comments=[ReviewComment(**c) for c in data.get("comments", [])])

    async def _post_review(self, repo, pr_number, review):
        gh_comments = [{"path": c.file, "line": c.line,
            "body": f"[{c.severity.upper()}] **{c.category}**: {c.body}"}
            for c in review.comments]
        event = "APPROVE" if review.approved else "COMMENT"
        await self.http.post(
            f"https://api.github.com/repos/{repo}/pulls/{pr_number}/reviews",
            json={"body": f"## Code Review\n{review.summary}\n**Score:** {review.score}/100",
                  "event": event, "comments": gh_comments})
עשו עכשיו 15 דקות

קחו את ה-CodeReviewAgent למעלה והריצו על PR אמיתי. אתם צריכים GitHub Personal Access Token. השוו את ה-review של הסוכן ל-review שאתם הייתם נותנים. מה הוא תפס שאתם לא? מה הוא פספס?

מימוש ב-TypeScript עם Anthropic SDK

// TypeScript - Code Review Agent with Anthropic SDK + Tool Use
import Anthropic from "@anthropic-ai/sdk";
import { Octokit } from "@octokit/rest";

const anthropic = new Anthropic();
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });

const tools: Anthropic.Tool[] = [
  { name: "get_pr_diff", description: "Fetch PR diff",
    input_schema: { type: "object" as const, properties: {
      owner: { type: "string" }, repo: { type: "string" },
      pull_number: { type: "number" } }, required: ["owner", "repo", "pull_number"] } },
  { name: "get_changed_files", description: "List changed files in PR",
    input_schema: { type: "object" as const, properties: {
      owner: { type: "string" }, repo: { type: "string" },
      pull_number: { type: "number" } }, required: ["owner", "repo", "pull_number"] } },
  { name: "post_review", description: "Post inline review comments on PR",
    input_schema: { type: "object" as const, properties: {
      owner: { type: "string" }, repo: { type: "string" },
      pull_number: { type: "number" }, body: { type: "string" },
      comments: { type: "array", items: { type: "object", properties: {
        path: { type: "string" }, line: { type: "number" }, body: { type: "string" } } } },
      event: { type: "string", enum: ["APPROVE", "REQUEST_CHANGES", "COMMENT"] }
    }, required: ["owner", "repo", "pull_number", "body", "comments", "event"] } },
];

async function executeTool(name: string, input: Record<string, any>): Promise<string> {
  switch (name) {
    case "get_pr_diff": {
      const { data } = await octokit.pulls.get({ owner: input.owner, repo: input.repo,
        pull_number: input.pull_number, mediaType: { format: "diff" } });
      return String(data).substring(0, 50000); }
    case "get_changed_files": {
      const { data } = await octokit.pulls.listFiles({ owner: input.owner,
        repo: input.repo, pull_number: input.pull_number });
      return JSON.stringify(data.map(f => ({ filename: f.filename, status: f.status,
        additions: f.additions, deletions: f.deletions, patch: f.patch?.substring(0, 5000) }))); }
    case "post_review": {
      await octokit.pulls.createReview({ owner: input.owner, repo: input.repo,
        pull_number: input.pull_number, body: input.body, comments: input.comments,
        event: input.event });
      return JSON.stringify({ success: true }); }
    default: return JSON.stringify({ error: `Unknown tool: ${name}` });
  }
}

// Agent loop -- tool calling until done
async function reviewPR(owner: string, repo: string, prNumber: number) {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: `Review PR #${prNumber} in ${owner}/${repo}.
Fetch diff, analyze, post inline comments. Max 5-7 comments. Use [CRITICAL], [HIGH], [MEDIUM], [NIT].` }];

  let continueLoop = true;
  while (continueLoop) {
    const response = await anthropic.messages.create({
      model: "claude-sonnet-4-6-20260325", max_tokens: 4096,
      system: "You are an expert code reviewer. Fetch PR data, analyze, post review.",
      tools, messages });

    messages.push({ role: "assistant", content: response.content });
    const toolCalls = response.content.filter(
      (b): b is Anthropic.ToolUseBlock => b.type === "tool_use");

    if (toolCalls.length === 0 || response.stop_reason === "end_turn") { continueLoop = false; break; }

    const results: Anthropic.ToolResultBlockParam[] = [];
    for (const tc of toolCalls) {
      const result = await executeTool(tc.name, tc.input as Record<string, any>);
      results.push({ type: "tool_result", tool_use_id: tc.id, content: result });
    }
    messages.push({ role: "user", content: results });
  }
}
עשו עכשיו 10 דקות

השוו את שתי הגישות -- Python (direct flow) מול TypeScript (agentic tool-calling loop). רשמו 3 הבדלים מהותיים. באיזו גישה הסוכן "חכם" יותר? (רמז: ב-TS, הסוכן מחליט בעצמו מתי לקרוא ל-tools)

Security Analysis -- סריקת פגיעויות

advanced35 דקותpractice

Security review הוא ה-use case שבו הסוכן מוסיף הכי הרבה ערך. מפתחים אנושיים מתמקדים בפונקציונליות. Security issues נופלים בין הכיסאות. סוכן שסורק כל PR באופן אוטומטי הוא הראשון שרואה קוד חדש.

4 שכבות Security Scanning

שכבהמה בודקיםכלידוגמה
1. Secret DetectionAPI keys, passwords, tokensRegex + LLMAWS_SECRET = "AKIA..."
2. Code VulnerabilitiesSQL injection, XSS, path traversal, SSRFLLM + OWASP patternsquery(f"SELECT * WHERE id={user_input}")
3. Dependency Scanningפגיעויות ידועות ב-packages (CVEs)npm audit / pip-audit + LLMlodash 4.17.20 -- CVE-2021-23337
4. Configuration IssuesCORS permissive, debug mode, weak cryptoLLM config analysisCORS: { origin: "*" }
# Python - Security Scanner Agent
import re
from dataclasses import dataclass

@dataclass
class SecurityFinding:
    severity: str       # "critical", "high", "medium", "low"
    category: str       # "secret", "vulnerability", "dependency", "config"
    file: str
    line: int
    title: str
    description: str
    recommendation: str
    cwe: str = ""

class SecurityAgent:
    """Security Scanner -- scans PR diffs for vulnerabilities."""

    SECRET_PATTERNS = [
        (r'(?i)(api[_-]?key|api[_-]?secret)\s*[=:]\s*["\']?[a-zA-Z0-9]{20,}', "API Key/Secret"),
        (r'(?i)(password|passwd|pwd)\s*[=:]\s*["\'][^"\']+["\']', "Hardcoded password"),
        (r'AKIA[0-9A-Z]{16}', "AWS Access Key ID"),
        (r'(?i)(sk-[a-zA-Z0-9]{20,})', "OpenAI/Stripe Secret Key"),
        (r'ghp_[a-zA-Z0-9]{36}', "GitHub Personal Access Token"),
        (r'-----BEGIN (RSA |EC )?PRIVATE KEY-----', "Private key"),
    ]

    VULN_PATTERNS = {
        "sql_injection": {
            "patterns": [r'f".*SELECT.*\{.*\}"', r"f'.*SELECT.*\{.*\}'",
                         r'execute\s*\(\s*[f"\'`].*\{'],
            "title": "Potential SQL Injection", "cwe": "CWE-89", "severity": "critical"},
        "xss": {
            "patterns": [r'document\.write\s*\(', r'v-html\s*='],
            "title": "Potential Cross-Site Scripting (XSS)", "cwe": "CWE-79", "severity": "high"},
        "path_traversal": {
            "patterns": [r'open\s*\(\s*(?:request|req|user|input)'],
            "title": "Potential Path Traversal", "cwe": "CWE-22", "severity": "high"},
        "ssrf": {
            "patterns": [r'requests\.get\s*\(\s*(?:user|input|request)'],
            "title": "Potential SSRF", "cwe": "CWE-918", "severity": "high"},
    }

    def __init__(self):
        self.client = anthropic.Anthropic()

    async def scan(self, repo: str, pr_number: int) -> list[SecurityFinding]:
        diff = await self._get_diff(repo, pr_number)
        findings = self._regex_scan(diff)
        findings.extend(await self._llm_security_scan(diff))
        return self._deduplicate(findings)

    def _regex_scan(self, diff: str) -> list[SecurityFinding]:
        findings = []
        current_file, line_num = "", 0
        for line in diff.split("\n"):
            if line.startswith("+++ b/"): current_file = line[6:]; line_num = 0; continue
            if line.startswith("@@"):
                m = re.search(r'\+(\d+)', line)
                if m: line_num = int(m.group(1))
                continue
            if not line.startswith("+") or line.startswith("+++"):
                if not line.startswith("-"): line_num += 1
                continue
            added = line[1:]; line_num += 1

            for pattern, desc in self.SECRET_PATTERNS:
                if re.search(pattern, added):
                    findings.append(SecurityFinding(severity="critical", category="secret",
                        file=current_file, line=line_num, title="Secret Detected",
                        description=f"{desc}: {added.strip()[:80]}",
                        recommendation="Remove secret. Rotate key. Use env vars.", cwe="CWE-798"))

            for vtype, vinfo in self.VULN_PATTERNS.items():
                for pat in vinfo["patterns"]:
                    if re.search(pat, added):
                        findings.append(SecurityFinding(severity=vinfo["severity"],
                            category="vulnerability", file=current_file, line=line_num,
                            title=vinfo["title"], description=f"Pattern: {added.strip()[:80]}",
                            recommendation=f"Review for {vtype}. Use parameterized queries.",
                            cwe=vinfo["cwe"]))
        return findings

    async def _llm_security_scan(self, diff: str) -> list[SecurityFinding]:
        response = self.client.messages.create(
            model="claude-sonnet-4-6-20260325", max_tokens=3000,
            system="You are a security expert. Find: business logic flaws, race conditions, "
                   "insecure deserialization, info leakage, missing validation. "
                   "Return JSON array of findings or []. Only confident findings.",
            messages=[{"role": "user", "content": f"Review:\n{diff[:30000]}"}])
        try:
            m = re.search(r'\[.*\]', response.content[0].text, re.DOTALL)
            if m: return [SecurityFinding(category="vulnerability", **i) for i in json.loads(m.group(0))]
        except: pass
        return []

    def _deduplicate(self, findings):
        seen, unique = set(), []
        for f in sorted(findings, key=lambda x: {"critical":0,"high":1,"medium":2,"low":3}[x.severity]):
            key = (f.file, f.line, f.category)
            if key not in seen: seen.add(key); unique.append(f)
        return unique
עשו עכשיו 10 דקות

צרו קובץ test עם 3 פגיעויות מכוונות: (1) hardcoded API key, (2) SQL injection עם f-string, (3) XSS-prone DOM manipulation. הכניסו ל-PR ובדקו שה-SecurityAgent מוצא את כולם.

Test Generation Agent -- יצירת טסטים אוטומטית

advanced35 דקותpractice

מפתחים לא אוהבים לכתוב tests. ה-test generation agent מייצר draft ראשוני -- במקום 30-60 דקות מאפס, המפתח עורך tests קיימים ב-5-10 דקות.

אסטרטגיות יצירת Tests

אסטרטגיהמה מייצריםדוגמה
Happy PathFunction עובדת עם input תקיןtest_create_user_success()
Edge Casesריק, null, ערכים קיצונייםtest_create_user_empty_name()
Error CasesErrors מטופלים נכוןtest_create_user_duplicate_email()
Boundary Values0, -1, MAX_INTtest_pagination_zero_page()
Integrationאינטראקציה בין componentstest_user_signup_sends_email()
# Python - Test Generation Agent
class TestGenerationAgent:
    SYSTEM_PROMPT = """You are an expert test engineer. Generate comprehensive unit tests.
Rules: test every new/modified function, cover happy/edge/error/boundary cases,
use pytest (Python) or Jest (TS/JS), include mocking, 3-5 tests per function."""

    async def generate(self, repo, pr_number) -> dict:
        files = await self._get_changed_files(repo, pr_number)
        test_files = {}
        for f in files:
            if self._is_testable(f["filename"]):
                content = await self._get_file_content(repo, f["filename"])
                tests = await self._generate_tests(f["filename"], content, f.get("patch", ""))
                if tests: test_files[self._test_filename(f["filename"])] = tests
        return {"test_files": test_files, "summary": f"Generated tests for {len(test_files)} files"}

    def _is_testable(self, fn):
        if any(x in fn for x in ["test_", "_test.", ".test.", "spec."]): return False
        return any(fn.endswith(e) for e in [".py", ".ts", ".js", ".tsx", ".jsx"])

דוגמה לפלט -- test file שהסוכן מייצר:

# Generated by DevOps Agent -- Tests for user_service.py
import pytest
from unittest.mock import AsyncMock
from user_service import UserService

@pytest.fixture
def user_service():
    return UserService(db=AsyncMock())

class TestCreateUser:
    async def test_success(self, user_service):
        """Happy path: valid user creation."""
        user_service.db.insert.return_value = {"id": "usr_123", "name": "Yael Cohen"}
        result = await user_service.create_user(name="Yael Cohen", email="yael@example.co.il")
        assert result["name"] == "Yael Cohen"

    async def test_duplicate_email(self, user_service):
        """Error: duplicate email raises ValueError."""
        user_service.db.insert.side_effect = ValueError("duplicate key")
        with pytest.raises(ValueError, match="duplicate"):
            await user_service.create_user(name="Test", email="existing@example.com")

    async def test_empty_name(self, user_service):
        """Edge case: empty name rejected."""
        with pytest.raises(ValueError, match="name"):
            await user_service.create_user(name="", email="test@example.com")

    async def test_invalid_email(self, user_service):
        """Boundary: invalid email format rejected."""
        with pytest.raises(ValueError, match="email"):
            await user_service.create_user(name="Test", email="not-an-email")

יצירת Tests ב-TypeScript/Jest

הסוכן לא מוגבל ל-Python. הנה דוגמה לקוד שהסוכן מייצר עבור TypeScript service עם Jest. שימו לב לשימוש ב-mocks, type safety, ו-async/await שמתאימים ל-ecosystem:

// Generated by DevOps Agent -- Tests for PaymentService.ts
import { PaymentService } from '../services/PaymentService';
import { StripeClient } from '../clients/StripeClient';

// Mock the Stripe client
jest.mock('../clients/StripeClient');
const MockStripeClient = StripeClient as jest.MockedClass<typeof StripeClient>;

describe('PaymentService', () => {
  let service: PaymentService;
  let mockStripe: jest.Mocked<StripeClient>;

  beforeEach(() => {
    mockStripe = new MockStripeClient() as jest.Mocked<StripeClient>;
    service = new PaymentService(mockStripe);
  });

  describe('processPayment', () => {
    it('should process valid payment and return confirmation', async () => {
      // Happy path
      mockStripe.charge.mockResolvedValue({ id: 'ch_123', status: 'succeeded' });
      const result = await service.processPayment({ amount: 5000, currency: 'ILS', customerId: 'cus_1' });
      expect(result.status).toBe('succeeded');
      expect(mockStripe.charge).toHaveBeenCalledWith(expect.objectContaining({ amount: 5000 }));
    });

    it('should throw on negative amount', async () => {
      // Boundary: negative amount
      await expect(service.processPayment({ amount: -100, currency: 'ILS', customerId: 'cus_1' }))
        .rejects.toThrow('Amount must be positive');
    });

    it('should throw on zero amount', async () => {
      // Boundary: zero amount
      await expect(service.processPayment({ amount: 0, currency: 'ILS', customerId: 'cus_1' }))
        .rejects.toThrow('Amount must be positive');
    });

    it('should handle Stripe timeout gracefully', async () => {
      // Error case: external service timeout
      mockStripe.charge.mockRejectedValue(new Error('Request timeout'));
      const result = await service.processPayment({ amount: 5000, currency: 'ILS', customerId: 'cus_1' });
      expect(result.status).toBe('failed');
      expect(result.error).toContain('timeout');
    });

    it('should handle card declined', async () => {
      // Error case: business logic error
      mockStripe.charge.mockRejectedValue({ type: 'card_error', code: 'card_declined' });
      const result = await service.processPayment({ amount: 5000, currency: 'ILS', customerId: 'cus_1' });
      expect(result.status).toBe('declined');
    });

    it('should enforce maximum transaction amount', async () => {
      // Boundary: max amount
      await expect(service.processPayment({ amount: 10_000_001, currency: 'ILS', customerId: 'cus_1' }))
        .rejects.toThrow('exceeds maximum');
    });
  });
});

Smart Test Generation -- בחירת אסטרטגיה לפי סוג הקוד

לא כל קוד צריך את אותן בדיקות. הסוכן מזהה את סוג הקוד ומתאים את אסטרטגיית היצירה:

סוג קודאסטרטגיה עיקריתדגש מיוחדדוגמת Test
API EndpointRequest/Response testingStatus codes, validation, authtest_get_user_returns_404_when_not_found()
Business LogicState transitionsEdge cases, invariantstest_order_cannot_ship_when_unpaid()
Data ProcessingInput/Output pairsEmpty data, malformed inputtest_parse_csv_with_missing_columns()
IntegrationMock external servicesTimeouts, retries, error responsestest_payment_retries_on_timeout()
ConfigurationValidation testsMissing required fields, invalid valuestest_config_rejects_empty_db_url()
# Python - Smart Test Strategy Selection
class TestGenerationAgent:
    # ... (init and generate from above)

    def _select_strategy(self, filename: str, content: str) -> str:
        """Choose test generation strategy based on code type."""
        strategies = []
        # Detect API endpoints
        if any(kw in content for kw in ["@app.route", "@router.", "app.get(", "app.post("]):
            strategies.append("api_endpoint")
        # Detect data models
        if any(kw in content for kw in ["class.*Model", "dataclass", "interface ", "type "]):
            strategies.append("data_validation")
        # Detect business logic
        if any(kw in content for kw in ["if.*elif", "state", "status", "transition"]):
            strategies.append("state_machine")
        # Detect external integrations
        if any(kw in content for kw in ["requests.", "httpx.", "fetch(", "axios."]):
            strategies.append("integration_mock")
        return ", ".join(strategies) if strategies else "general"

    async def _generate_tests(self, filename, content, patch) -> str:
        strategy = self._select_strategy(filename, content)
        response = self.client.messages.create(
            model="claude-sonnet-4-6-20260325", max_tokens=4000,
            system=f"""{self.SYSTEM_PROMPT}
Strategy: {strategy}
- api_endpoint: test all HTTP methods, status codes, request validation, auth
- data_validation: test required fields, types, constraints, serialization
- state_machine: test all valid transitions, invalid transitions, edge states
- integration_mock: mock external calls, test timeouts, retries, error handling
- general: happy path, edge cases, error cases, boundary values""",
            messages=[{"role": "user",
                "content": f"Generate tests for:\n## {filename}\n```\n{content}\n```\n## Changes:\n{patch}"}])
        return response.content[0].text
עשו עכשיו 10 דקות

קחו function מפרויקט שלכם שאין לה tests. תנו אותה ל-Claude עם ה-system prompt. האם ה-tests רצים? הריצו pytest/jest. כמה עברו? מה צריך לתקן?

Deployment Assistant Agent

advanced35 דקותpractice

Deployment הוא הרגע שבו שינויי קוד הופכים לבעיות אמיתיות. ה-Deploy Assistant עוזר ב-3 שלבים: pre-deploy checks, deploy monitoring, ו-rollback detection.

Pre-Deploy Checklist

#Checkאוטומטי?מה בודקים
1Tests passingVכל ה-test suites עוברים
2No security issuesVאין critical/high findings
3Code reviewedVלפחות reviewer אחד אישר
4No merge conflictsVBranch up to date
5Version bumpedSemiVersion עודכן
6Changelog updatedSemiCHANGELOG.md מעודכן
7DB migrationsManualBackwards compatible?
8Feature flagsManualמוגדרים נכון?
# Python - Deployment Assistant
class DeployAssistant:
    async def pre_deploy_check(self, repo, branch="main") -> dict:
        checks = {}
        ci = await self._check_ci_status(repo, branch)
        checks["ci_passing"] = ci["all_passing"]
        security = await self._check_security(repo, branch)
        checks["no_critical_security"] = security["critical_count"] == 0
        checks["approved"] = (await self._check_approval(repo, branch))["is_approved"]
        checks["up_to_date"] = await self._check_up_to_date(repo, branch)
        checks["deploy_ready"] = all(checks.values())
        return checks

    async def monitor_deploy(self, environment="production", duration_minutes=15) -> dict:
        import asyncio
        baseline = await self._get_baseline_metrics(environment)
        alerts = []
        for minute in range(duration_minutes):
            await asyncio.sleep(60)
            current = await self._get_current_metrics(environment)
            anomalies = self._detect_anomalies(baseline, current)
            if anomalies:
                alerts.extend(anomalies)
                critical = [a for a in anomalies if a["severity"] == "critical"]
                if critical:
                    return {"status": "rollback_recommended", "reason": critical[0]["description"]}
        return {"status": "healthy", "duration_minutes": duration_minutes}

    def _detect_anomalies(self, baseline, current):
        anomalies = []
        if current.get("error_rate", 0) > baseline.get("error_rate", 1) * 3:
            anomalies.append({"severity": "critical", "metric": "error_rate",
                "description": f"Error rate spiked to {current['error_rate']:.1%}"})
        if current.get("p99_latency", 0) > baseline.get("p99_latency", 100) * 2:
            anomalies.append({"severity": "high", "metric": "p99_latency",
                "description": f"P99 latency: {current['p99_latency']}ms"})
        if current.get("rps", 0) < baseline.get("rps", 1) * 0.5:
            anomalies.append({"severity": "critical", "metric": "request_volume",
                "description": f"Request volume dropped to {current['rps']} rps"})
        return anomalies

Rollback Automation

כש-anomaly detection מזהה בעיה קריטית, ה-Deploy Assistant צריך לבצע rollback אוטומטי -- או לפחות להציע אחד ולחכות לאישור אנושי. הנה המימוש:

# Python - Rollback Manager
class RollbackManager:
    """Manages deployment rollbacks with safety checks."""

    def __init__(self, github_token: str, slack_webhook: str = None):
        self.http = httpx.AsyncClient(
            headers={"Authorization": f"token {github_token}"})
        self.slack_webhook = slack_webhook
        self.client = anthropic.Anthropic()

    async def evaluate_rollback(self, environment: str, anomalies: list) -> dict:
        """Decide whether to rollback based on anomaly severity."""
        critical = [a for a in anomalies if a["severity"] == "critical"]
        high = [a for a in anomalies if a["severity"] == "high"]

        if len(critical) >= 1:
            action = "auto_rollback"
            reason = f"{len(critical)} critical anomalies detected"
        elif len(high) >= 3:
            action = "auto_rollback"
            reason = f"{len(high)} high-severity anomalies detected"
        elif len(high) >= 1:
            action = "suggest_rollback"
            reason = f"{len(high)} high-severity anomalies -- review recommended"
        else:
            action = "monitor"
            reason = "No critical issues, continuing monitoring"

        decision = {"action": action, "reason": reason, "anomalies": anomalies}

        if action == "auto_rollback":
            result = await self._execute_rollback(environment)
            decision["rollback_result"] = result
            await self._notify_team(environment, decision)
        elif action == "suggest_rollback":
            await self._notify_team(environment, decision)

        return decision

    async def _execute_rollback(self, environment: str) -> dict:
        """Execute rollback to previous stable version."""
        previous = await self._get_previous_deployment(environment)
        if not previous:
            return {"status": "failed", "reason": "No previous deployment found"}
        # Trigger redeployment of previous version
        await self.http.post(
            f"https://api.github.com/repos/{previous['repo']}/deployments",
            json={"ref": previous["sha"], "environment": environment,
                  "description": f"Rollback from {previous['current_sha'][:7]}",
                  "auto_merge": False, "required_contexts": []})
        return {"status": "initiated", "previous_sha": previous["sha"],
                "rolled_back_sha": previous["current_sha"]}

    async def _notify_team(self, environment: str, decision: dict):
        """Send Slack notification about rollback decision."""
        if not self.slack_webhook: return
        emoji = {"auto_rollback": "🚨", "suggest_rollback": "⚠️", "monitor": "👀"}
        await self.http.post(self.slack_webhook, json={"text":
            f"{emoji.get(decision['action'], '📋')} *Deploy {decision['action']}* "
            f"({environment})\n{decision['reason']}"})

Canary Deployment Support

במקום "all or nothing", deploy agent יכול לנהל canary deployments -- להפנות 5% מהתעבורה לגרסה החדשה, לנטר, ולהגדיל בהדרגה:

שלבתעבורהמשךCriteria להמשך
1. Canary5%10 דקותError rate < baseline * 1.5
2. Partial25%15 דקותP99 latency < baseline * 1.3
3. Majority75%15 דקותNo critical alerts
4. Full100%--All metrics stable
# Python - Canary Deployment Controller
class CanaryController:
    STAGES = [
        {"name": "canary",   "traffic_pct": 5,   "duration_min": 10},
        {"name": "partial",  "traffic_pct": 25,  "duration_min": 15},
        {"name": "majority", "traffic_pct": 75,  "duration_min": 15},
        {"name": "full",     "traffic_pct": 100, "duration_min": 0},
    ]

    async def run_canary(self, environment, new_version, baseline_metrics):
        for stage in self.STAGES:
            await self._set_traffic_split(environment, new_version, stage["traffic_pct"])
            if stage["duration_min"] == 0:
                return {"status": "complete", "version": new_version}
            # Monitor for the stage duration
            result = await self._monitor_stage(
                environment, baseline_metrics, stage["duration_min"])
            if result["status"] != "healthy":
                await self._set_traffic_split(environment, new_version, 0)  # rollback
                return {"status": "rolled_back", "failed_stage": stage["name"],
                        "reason": result.get("reason")}
        return {"status": "complete", "version": new_version}
עשו עכשיו 10 דקות

כתבו pre-deploy checklist לפרויקט שלכם. לפחות 8 פריטים, מסווגים: automated / semi / manual. חשבו גם על rollback strategy -- תוך כמה דקות צריך להיות rollback מלא?

Production Monitoring Agent

advanced30 דקותpractice

ב-3 בלילה מגיע alert. ה-Monitoring Agent עושה את הניתוח אוטומטית -- קורא metrics, מתאם עם recent deploys, מחפש ב-logs, ומציע diagnosis.

# Python - Production Monitoring Agent
class MonitorAgent:
    SYSTEM_PROMPT = """You are a senior SRE analyzing a production incident.
Provide: 1) Root Cause Analysis (confidence: high/medium/low),
2) Impact Assessment, 3) Immediate Actions, 4) Resolution Steps, 5) Prevention."""

    RUNBOOKS = {
        "high_error_rate": [
            "1. Check for recent deploys (last 2 hours)",
            "2. Compare error signatures before/after deploy",
            "3. If new errors: rollback to previous version",
            "4. Check DB connection pool -- exhaustion causes 500s",
            "5. Check memory/CPU -- OOM kills cause cascading failures"],
        "high_latency": [
            "1. Check database query times",
            "2. Check external API response times",
            "3. Check CPU/memory -- scale if above 90%",
            "4. Check for lock contention in database",
            "5. Check CDN/cache hit rates"],
        "service_down": [
            "1. Check pod status: kubectl get pods -n production",
            "2. Check for OOM kills: kubectl describe pod",
            "3. Check crash loops: kubectl logs --previous",
            "4. If all down: check node health, cloud provider",
            "5. Attempt restart: kubectl rollout restart"],
    }

    async def analyze_alert(self, event) -> dict:
        alert_type = event.get("alert_type", "unknown")
        service = event.get("service", "unknown")
        context = await self._gather_context(service, alert_type)
        runbook = self.RUNBOOKS.get(alert_type)

        response = self.client.messages.create(
            model="claude-sonnet-4-6-20260325", max_tokens=2000,
            system=self.SYSTEM_PROMPT,
            messages=[{"role": "user", "content":
                f"Service: {service}, Alert: {alert_type}\n"
                f"Metrics: {json.dumps(context['current_metrics'])}\n"
                f"Baseline: {json.dumps(context['baseline_metrics'])}\n"
                f"Deploys: {json.dumps(context['recent_deploys'])}\n"
                f"Logs: {chr(10).join(context['recent_logs'])}\n"
                f"Runbook: {chr(10).join(runbook) if runbook else 'None'}"}])
        return {"analysis": response.content[0].text, "service": service}

Context Gathering -- המידע שהסוכן צריך

האיכות של incident analysis תלויה בכמות ואיכות המידע שהסוכן מקבל. הנה הפונקציה שאוספת את כל ה-context הנדרש:

# Python - Monitoring Context Gathering
class MonitorAgent:
    # ... (SYSTEM_PROMPT and RUNBOOKS from above)

    async def _gather_context(self, service: str, alert_type: str) -> dict:
        """Gather all relevant context for incident analysis."""
        import asyncio
        # Fetch all context in parallel for speed
        metrics_task = asyncio.create_task(self._get_metrics(service))
        baseline_task = asyncio.create_task(self._get_baseline(service))
        deploys_task = asyncio.create_task(self._get_recent_deploys(service))
        logs_task = asyncio.create_task(self._get_error_logs(service))
        dependencies_task = asyncio.create_task(self._check_dependencies(service))

        return {
            "current_metrics": await metrics_task,
            "baseline_metrics": await baseline_task,
            "recent_deploys": await deploys_task,
            "recent_logs": await logs_task,
            "dependency_status": await dependencies_task,
        }

    async def _get_metrics(self, service: str) -> dict:
        """Fetch current metrics from monitoring system (Datadog/Grafana)."""
        # Example: Datadog API query
        resp = await self.http.get(
            "https://api.datadoghq.com/api/v1/query",
            params={"from": int(time.time()) - 900, "to": int(time.time()),
                    "query": f"avg:http.request.duration{{service:{service}}}"},
            headers={"DD-API-KEY": os.environ["DD_API_KEY"]})
        data = resp.json()
        return {
            "error_rate": self._extract_latest(data, "error_rate"),
            "p50_latency": self._extract_latest(data, "p50"),
            "p99_latency": self._extract_latest(data, "p99"),
            "rps": self._extract_latest(data, "request_count"),
            "cpu_percent": self._extract_latest(data, "cpu"),
            "memory_percent": self._extract_latest(data, "memory"),
        }

    async def _check_dependencies(self, service: str) -> list[dict]:
        """Check health of dependent services (DB, cache, external APIs)."""
        deps = self._get_service_dependencies(service)
        results = []
        for dep in deps:
            try:
                resp = await self.http.get(dep["health_url"], timeout=5.0)
                results.append({"name": dep["name"], "status": "healthy",
                    "latency_ms": resp.elapsed.total_seconds() * 1000})
            except Exception as e:
                results.append({"name": dep["name"], "status": "unhealthy", "error": str(e)})
        return results

Incident Notification Pipeline

ניתוח incident לבד לא מספיק -- צריך גם להודיע לאנשים הנכונים, בערוץ הנכון, עם רמת הדחיפות הנכונה:

# Python - Incident Notification Pipeline
class IncidentNotifier:
    """Routes incident notifications to the right channels."""

    SEVERITY_ROUTING = {
        "critical": {
            "channels": ["slack_oncall", "pagerduty"],
            "mention": "@oncall-team",
            "color": "#FF0000"
        },
        "high": {
            "channels": ["slack_engineering"],
            "mention": "@engineering",
            "color": "#FF8C00"
        },
        "medium": {
            "channels": ["slack_engineering"],
            "mention": "",
            "color": "#FFD700"
        },
    }

    async def notify(self, severity: str, analysis: dict, service: str):
        routing = self.SEVERITY_ROUTING.get(severity, self.SEVERITY_ROUTING["medium"])
        message = self._format_incident_message(severity, analysis, service)
        for channel in routing["channels"]:
            if channel == "pagerduty":
                await self._trigger_pagerduty(severity, message, service)
            elif channel.startswith("slack_"):
                await self._send_slack(channel, message, routing)

    def _format_incident_message(self, severity, analysis, service) -> str:
        return (f"*[{severity.upper()}] Incident on {service}*\n"
                f"*Root Cause:* {analysis.get('root_cause', 'Investigating')}\n"
                f"*Impact:* {analysis.get('impact', 'Unknown')}\n"
                f"*Actions:* {analysis.get('immediate_actions', 'See thread')}\n"
                f"*Recent Deploy:* {analysis.get('related_deploy', 'None detected')}")

    async def _trigger_pagerduty(self, severity, message, service):
        await self.http.post("https://events.pagerduty.com/v2/enqueue",
            json={"routing_key": os.environ["PAGERDUTY_KEY"],
                  "event_action": "trigger",
                  "payload": {"summary": message[:1024], "severity": severity,
                              "source": service, "component": "devops-agent"}})
עשו עכשיו 10 דקות

כתבו runbook לincident נפוץ בפרויקט שלכם. 5-7 צעדים ברורים. לכל צעד: מה בודקים, criteria, ומה עושים. חשבו גם -- מי צריך לקבל notification ובאיזו רמת דחיפות?

CI/CD Integration ו-GitHub Actions

intermediate30 דקותpractice

בפרודקשן, הסוכנים צריכים לרוץ אוטומטית על כל PR. GitHub Actions הוא הדרך הטבעית.

# .github/workflows/devops-agent.yml
name: DevOps Agent Review
on:
  pull_request:
    types: [opened, synchronize, reopened]
permissions:
  pull-requests: write
  contents: read
jobs:
  ai-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: actions/setup-python@v5
        with: { python-version: '3.11' }
      - run: pip install anthropic httpx
      - name: Run DevOps Agent
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: python scripts/run_review.py --repo "${{ github.repository }}" --pr "${{ github.event.pull_request.number }}"

Webhook Setup -- הגדרת GitHub Webhook צעד אחרי צעד

אם אתם רוצים שהסוכן ירוץ מ-server שלכם (במקום GitHub Actions), תצטרכו להגדיר GitHub Webhook. זה התהליך המלא:

שלב 1: הגדרת Webhook ב-GitHub

# אפשר להגדיר webhook דרך ה-API:
curl -X POST \
  -H "Authorization: token YOUR_GITHUB_TOKEN" \
  -H "Accept: application/vnd.github.v3+json" \
  https://api.github.com/repos/OWNER/REPO/hooks \
  -d '{
    "name": "web",
    "active": true,
    "events": ["pull_request", "issue_comment", "deployment", "deployment_status"],
    "config": {
      "url": "https://your-server.com/webhook",
      "content_type": "json",
      "secret": "YOUR_WEBHOOK_SECRET",
      "insecure_ssl": "0"
    }
  }'

# או דרך ה-UI:
# 1. Settings -> Webhooks -> Add webhook
# 2. Payload URL: https://your-server.com/webhook
# 3. Content type: application/json
# 4. Secret: YOUR_WEBHOOK_SECRET (same as in your server)
# 5. Events: "Let me select individual events" -> Pull requests, Issue comments, Deployments

שלב 2: Webhook Server עם Signature Verification

// TypeScript - Complete Webhook Server
import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

// Signature verification middleware
function verifySignature(req: express.Request, res: express.Response, next: express.NextFunction) {
  const sig = req.headers["x-hub-signature-256"] as string;
  if (!sig) return res.status(401).send("Missing signature");

  const expected = "sha256=" + crypto.createHmac("sha256", process.env.WEBHOOK_SECRET!)
    .update(JSON.stringify(req.body)).digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
    return res.status(401).send("Invalid signature");

  next();
}

// Health check endpoint (useful for monitoring)
app.get("/health", (req, res) => res.json({ status: "ok", uptime: process.uptime() }));

// Main webhook handler
app.post("/webhook", verifySignature, async (req, res) => {
  // IMPORTANT: Respond immediately, then process async
  // GitHub expects a response within 10 seconds, agent processing takes longer
  res.status(200).send("OK");

  const event = req.headers["x-github-event"] as string;
  const action = req.body.action;
  const repo = req.body.repository?.full_name;
  const pr = req.body.pull_request?.number;

  console.log(`[Webhook] ${event}.${action} on ${repo} PR #${pr}`);

  try {
    // Route to appropriate handler
    switch (`${event}.${action}`) {
      case "pull_request.opened":
      case "pull_request.synchronize":
      case "pull_request.reopened":
        await handlePullRequest(repo, pr, req.body);
        break;
      case "issue_comment.created":
        if (req.body.issue?.pull_request && req.body.comment?.body.includes("@devops-agent"))
          await handleBotMention(repo, req.body.issue.number, req.body.comment);
        break;
      case "deployment.created":
        await handleDeployment(repo, req.body.deployment);
        break;
    }
  } catch (err) {
    console.error(`[Webhook] Error processing ${event}.${action}:`, err);
    // Don't throw -- we already sent 200. Log and alert instead.
  }
});

app.listen(3000, () => console.log("Webhook server running on :3000"));

שלב 3: הרצה עם ngrok לפיתוח מקומי

# For local development, expose your server via ngrok:
# 1. Install ngrok: https://ngrok.com/download
ngrok http 3000

# 2. Copy the https URL (e.g., https://abc123.ngrok-free.app)
# 3. Set it as the webhook URL in GitHub
# 4. Test by opening a PR

# For production, deploy to:
# - Railway / Render / Fly.io (simple, $5-10/month)
# - Cloud Run (auto-scale, pay per request)
# - Your existing VPS / Kubernetes cluster

Slack Notification Integration

הסוכן מייצר reviews ו-security findings, אבל בלי notifications אף אחד לא ידע. Slack הוא הערוץ הטבעי להודעות לצוות. הנה integration מלא שמתאים את ההודעה לפי סוג האירוע:

# Python - Slack Notification Helper
import httpx

class SlackNotifier:
    """Send formatted notifications to Slack channels."""

    def __init__(self, webhook_url: str):
        self.webhook_url = webhook_url
        self.http = httpx.AsyncClient()

    async def notify_review_complete(self, repo: str, pr_number: int, review: dict):
        """Notify team when code review is complete."""
        score = review.get("score", 0)
        color = "#36a64f" if score >= 80 else "#ff9900" if score >= 60 else "#ff0000"
        comment_count = len(review.get("comments", []))
        critical = sum(1 for c in review.get("comments", []) if c.get("severity") == "critical")

        blocks = [
            {"type": "header", "text": {"type": "plain_text",
                "text": f"Code Review: {repo} PR #{pr_number}"}},
            {"type": "section", "fields": [
                {"type": "mrkdwn", "text": f"*Score:* {score}/100"},
                {"type": "mrkdwn", "text": f"*Comments:* {comment_count}"},
                {"type": "mrkdwn", "text": f"*Critical Issues:* {critical}"},
                {"type": "mrkdwn", "text": f"*Status:* {'Approved' if review.get('approved') else 'Changes Requested'}"},
            ]},
            {"type": "actions", "elements": [
                {"type": "button", "text": {"type": "plain_text", "text": "View PR"},
                 "url": f"https://github.com/{repo}/pull/{pr_number}"}
            ]}
        ]
        await self.http.post(self.webhook_url, json={"blocks": blocks})

    async def notify_security_findings(self, repo: str, pr_number: int, findings: list):
        """Alert on security findings -- only critical and high."""
        critical_findings = [f for f in findings if f.severity in ("critical", "high")]
        if not critical_findings: return  # Don't spam for low-severity
        text = (f"*Security Alert: {repo} PR #{pr_number}*\n"
                f"Found {len(critical_findings)} critical/high issues:\n"
                + "\n".join(f"  - [{f.severity.upper()}] {f.title} in `{f.file}:{f.line}`"
                    for f in critical_findings[:5]))
        await self.http.post(self.webhook_url, json={"text": text})
Framework: GitHub Actions vs Webhook Server -- Decision Matrix
ממדGitHub ActionsWebhook Server
SetupLow -- YAML fileHigh -- server, hosting, SSL
CostFree tier (2000 min/month)$5-20/month hosting
Latency15-30s (cold start)2-5s (always running)
StateLimitedFull (database, memory)
CustomizationMediumFull

המלצה: התחילו עם GitHub Actions. עברו ל-webhook רק אם צריכים latency נמוך או state management.

עשו עכשיו 15 דקות

צרו workflow YAML ב-repo שלכם. הוסיפו ANTHROPIC_API_KEY כsecret. פתחו PR ובדקו שרץ.

Context, Learning, ושיפור מתמיד

intermediate25 דקותconcept + practice

סוכן review שלא מכיר את ה-codebase הוא כמו reviewer חיצוני ליום אחד. הוא יתפוס bugs גנריים, אבל יפספס context: "אצלנו תמיד Result type, לא exceptions", "camelCase לAPI, snake_case לDB."

3 שכבות Context

שכבהמקורמתי
RepositoryREADME, CONTRIBUTING, .eslintrcכל review (cached)
HistoricalPast reviews, common issuesכל review (DB)
Sessionה-PR הספציפי, commentsכל review (specific)
# Python - Context Manager
import sqlite3

class ReviewContextManager:
    def __init__(self, db_path="devops_agent.db"):
        self.db = sqlite3.connect(db_path)
        self.db.executescript("""
            CREATE TABLE IF NOT EXISTS review_history (
                id INTEGER PRIMARY KEY, repo TEXT, pr_number INTEGER,
                file_path TEXT, comment_body TEXT, severity TEXT, category TEXT,
                human_agreed BOOLEAN DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
            CREATE TABLE IF NOT EXISTS learned_rules (
                id INTEGER PRIMARY KEY, repo TEXT, rule_text TEXT,
                source TEXT, confidence REAL DEFAULT 0.5, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
        """)

    def record_human_feedback(self, repo, comment_id, agreed):
        self.db.execute("UPDATE review_history SET human_agreed=? WHERE id=?", (agreed, comment_id))
        if not agreed:
            row = self.db.execute("SELECT comment_body, category FROM review_history WHERE id=?", (comment_id,)).fetchone()
            if row:
                self.db.execute("INSERT INTO learned_rules (repo, rule_text, source, confidence) VALUES (?,?,?,?)",
                    (repo, f"AVOID: {row[0][:200]} ({row[1]})", "human_correction", 0.8))
        self.db.commit()

    def get_context(self, repo):
        rules = self.db.execute("SELECT rule_text FROM learned_rules WHERE repo=? AND confidence>0.5 ORDER BY confidence DESC LIMIT 20", (repo,)).fetchall()
        return "## Learned Rules\n" + "\n".join(f"- {r[0]}" for r in rules) if rules else ""
עשו עכשיו 5 דקות

חשבו על 5 conventions של הפרויקט שלכם שreviewer חיצוני לא היה יודע. כתבו אותם כ-rules לסוכן.

Evaluation -- האם הסוכן באמת טוב?

intermediate25 דקותconcept + practice

האם הסוכן באמת מועיל? או שהוא מייצר noise? Evaluation מבדיל בין "כלי מרשים" ל"כלי שמשנה workflow."

4 מדדים קריטיים

מדדמה מודדיםTargetאיך
Precisionכמה הערות נכונות?>80%Human labels valid/FP
Recallכמה בעיות אמיתיות נמצאו?>60%Compare to human review
False Positive Rateכמה FPs?<20%Human reviews comments
Dev SatisfactionReviews מועילים?>70%Survey: helpful Y/N
# Python - Evaluation Framework
class ReviewEvaluator:
    def calculate_metrics(self, repo, days=30):
        stats = self.db.execute("""
            SELECT COUNT(*), SUM(CASE WHEN human_agreed=1 THEN 1 ELSE 0 END),
                   SUM(CASE WHEN human_agreed=0 THEN 1 ELSE 0 END)
            FROM review_history WHERE repo=? AND created_at>datetime('now',?)
        """, (repo, f"-{days} days")).fetchone()
        total, agreed, disagreed = stats[0], stats[1] or 0, stats[2] or 0
        reviewed = agreed + disagreed
        return {"precision": agreed/reviewed if reviewed else 0,
                "false_positive_rate": disagreed/reviewed if reviewed else 0}

Evaluation Report -- דוח חודשי אוטומטי

הסוכן צריך לייצר דוח evaluation חודשי שמראה מגמות לאורך זמן. הנה מימוש שמנתח את הנתונים ומייצר סיכום:

# Python - Monthly Evaluation Report Generator
class EvaluationReporter:
    def __init__(self, db_path="devops_agent.db"):
        self.db = sqlite3.connect(db_path)

    def generate_monthly_report(self, repo: str) -> dict:
        """Generate comprehensive monthly evaluation report."""
        metrics_30d = self._calculate_period_metrics(repo, 30)
        metrics_7d = self._calculate_period_metrics(repo, 7)
        metrics_prev_30d = self._calculate_period_metrics(repo, 60, offset=30)

        # Category breakdown -- which types of comments are most accurate?
        category_stats = self.db.execute("""
            SELECT category,
                   COUNT(*) as total,
                   SUM(CASE WHEN human_agreed=1 THEN 1 ELSE 0 END) as agreed,
                   SUM(CASE WHEN human_agreed=0 THEN 1 ELSE 0 END) as disagreed
            FROM review_history
            WHERE repo=? AND created_at>datetime('now','-30 days')
                AND human_agreed IS NOT NULL
            GROUP BY category ORDER BY total DESC
        """, (repo,)).fetchall()

        # Top false positive patterns -- what to fix in prompt
        fp_patterns = self.db.execute("""
            SELECT category, comment_body, COUNT(*) as count
            FROM review_history
            WHERE repo=? AND human_agreed=0 AND created_at>datetime('now','-30 days')
            GROUP BY category, substr(comment_body, 1, 100)
            ORDER BY count DESC LIMIT 5
        """, (repo,)).fetchall()

        return {
            "period": "30 days",
            "overall": metrics_30d,
            "trend": {
                "precision_delta": metrics_30d["precision"] - metrics_prev_30d["precision"],
                "fp_rate_delta": metrics_30d["false_positive_rate"] - metrics_prev_30d["false_positive_rate"],
            },
            "last_7_days": metrics_7d,
            "by_category": [{"category": c[0], "total": c[1],
                "precision": c[2]/(c[2]+c[3]) if (c[2]+c[3]) else 0} for c in category_stats],
            "top_fp_patterns": [{"category": p[0], "pattern": p[1][:100], "count": p[2]}
                for p in fp_patterns],
            "recommendations": self._generate_recommendations(metrics_30d, category_stats, fp_patterns),
        }

    def _generate_recommendations(self, metrics, categories, fp_patterns) -> list[str]:
        recs = []
        if metrics["precision"] < 0.8:
            recs.append("Precision below 80% -- review prompt instructions, add 'only confident findings'")
        if metrics["false_positive_rate"] > 0.2:
            recs.append(f"FP rate above 20% -- top FP category: {fp_patterns[0][0] if fp_patterns else 'unknown'}")
        for cat in categories:
            cat_precision = cat[2]/(cat[2]+cat[3]) if (cat[2]+cat[3]) else 0
            if cat_precision < 0.5 and cat[1] > 5:
                recs.append(f"Category '{cat[0]}' has {cat_precision:.0%} precision -- consider disabling or retuning")
        return recs

A/B Testing של Prompt Variations

כדי לשפר את הסוכן באופן שיטתי, הריצו A/B testing על prompt variations. זו הדרך היחידה לדעת אם שינוי ב-prompt באמת משפר תוצאות:

ניסויVariant A (control)Variant B (test)מדד
Comment limit"Max 5-7 comments""Max 3-5 comments, critical only"Precision, developer satisfaction
Context depthDiff onlyFull file + diffFalse positive rate
Severity filterAll severitiesCritical + High onlySignal-to-noise ratio
ToneDirect ("Fix this")Suggestive ("Consider...")Developer satisfaction
Israeli Dev Teams -- שיקולים מיוחדים

טעויות נפוצות -- ואיך להימנע מהן

beginner15 דקותconcept
טעות 1: "Review Everything" -- Agent שמגיב על הכל

מה קורה: 15-20 comments, רוב nit-picks.

למה רע: Alert fatigue. מפתחים מפסיקים לקרוא.

פתרון: מקסימום 5-7 comments. Prioritize: critical > high > medium.

טעות 2: "Hallucinated Bugs" -- bugs שלא קיימים

מה קורה: Agent מדווח bug שלא שם -- למשל null check שכבר קיים מחוץ ל-diff.

למה רע: False positives הורסים אמון.

פתרון: תמיד תנו קובץ מלא, לא רק diff. בprompt: "Only report confident issues."

טעות 3: Agent בלי Context -- "Outsider Reviewer"

מה קורה: Agent מציע patterns שמנוגדים לarchitecture decisions קיימים.

למה רע: מפתחים מאבדים אמון.

פתרון: Context system -- README, CONTRIBUTING, learned rules. עדכון חודשי.

טעות 4: Security Scanner עם יותר מדי False Positives

מה קורה: מדווח על כל DOM manipulation, כל eval, כל f-string -- גם כשבטוחים.

למה רע: "The Boy Who Cried Wolf" -- כשיהיה finding אמיתי, אף אחד לא ישים לב.

פתרון: שילוב regex + LLM. Regex מוצא candidates, LLM מפלטר FPs. רק findings ששניהם מסכימים = reported.

שגרת עבודה -- פרק 18
תדירותמשימהזמן
יומיבדקו שAgent רץ על PRs -- אין errors?2 דק'
שבועיסקרו 5 reviews -- precision? FP rate? developer reactions?15 דק'
שבועיבדקו security findings -- הכל handled?10 דק'
חודשיEvaluation report. עדכנו prompt ו-context30 דק'
חודשיסקרו generated tests -- כמה נכנסו ל-codebase?20 דק'
רבעוניROI: שעות reviewer שנחסכו, bugs שנתפסו, satisfaction1 שעה
אם אתם עושים רק דבר אחד מהפרק הזה 30 דקות

בנו את ה-Code Review Agent הבסיסי. קחו את CodeReviewAgent, הגדירו GitHub token, הריצו על PR אחד אמיתי. תראו inline comments ב-GitHub -- תוך דקות, לא ימים. אחרי שעובד, הוסיפו security ו-test generation כ-phase 2.

תרגילים

תרגיל 1: סוכן ביקורת קוד על PRs אמיתיים 60 דקות
  1. בנו CodeReviewAgent עם הכלים: git diff parser, file reader, LLM analyzer
  2. הריצו על 5 PRs אמיתיים מריפו שלכם (או ריפו קוד פתוח)
  3. לכל PR רשמו: מה הסוכן מצא, מה human reviewer מצא, מה חפף
  4. חשבו: precision (כמה מהממצאים רלוונטיים), recall (כמה באגים אמיתיים נתפסו), FP rate
  5. שפרו את ה-prompt עד שתגיעו ל: precision מעל 75%, FP rate מתחת ל-25%

תוצר: סוכן ביקורת קוד עובד + טבלת השוואה מול human review.

תרגיל 2: סורק אבטחה עם פגיעויות מכוונות 45 דקות
  1. צרו branch חדש עם 10 פגיעויות מכוונות: 5 secrets (API keys בקוד), 3 פגיעויות קוד (SQL injection, XSS, path traversal), 2 בעיות הגדרה (CORS פתוח, debug mode)
  2. הריצו את סוכן האבטחה על ה-branch
  3. מדדו recall — כמה מתוך 10 הפגיעויות זוהו?
  4. הוסיפו 5 שורות שנראות חשודות אבל בטוחות — מדדו FP rate
  5. כוונו את הסף עד שתקבלו recall מעל 80% עם FP מתחת ל-30%

תוצר: סורק אבטחה מכויל + דוח precision/recall.

תרגיל 3: שילוב ב-CI/CD מקצה לקצה 60 דקות
  1. צרו קובץ GitHub Actions workflow שמפעיל את הסוכן על כל PR חדש
  2. הגדירו secrets ב-GitHub: API key למודל, GitHub token לתגובות
  3. הגדירו שהסוכן כותב inline comments על שורות בעייתיות ב-PR
  4. פתחו PR לבדיקה — וודאו שהסוכן רץ אוטומטית ומגיב
  5. הוסיפו התראת Slack/Telegram כשהסוכן מוצא בעיה ברמת HIGH

תוצר: workflow YAML עובד + PR עם תגובות אוטומטיות מהסוכן.

תרגיל 4: סוכן DevOps מלא 90 דקות
  1. חברו את כל הסוכנים מהתרגילים הקודמים לסוכן אחד עם Event Router
  2. הגדירו תגובה לכל אירוע: PR Opened (review + security + tests), Merged (pre-deploy check), Deploy (monitor 15 דקות), Alert (analysis + runbook)
  3. בנו dashboard פשוט שמראה: כמה PRs נסרקו, כמה ממצאים, זמן תגובה ממוצע
  4. הריצו על 3 PRs אמיתיים ותעדו את התוצאות

תוצר: סוכן DevOps משולב עם GitHub Actions + dashboard.

בדוק את עצמך -- 5 שאלות
  1. מה ההבדל בין review agent שמשתמש ב-diff בלבד לעומת קובץ מלא + diff? דוגמה לFP מdiff-only?
  2. תארו 4 שכבות Security Scanning. למה regex לבד לא מספיק?
  3. מה 3 אסטרטגיות ליצירת tests? למה Happy Path לבד לא מספיק?
  4. הבדל בין GitHub Actions ל-Webhook Server. מתי כל אחד מתאים?
  5. מה Precision ו-False Positive Rate? למה FP rate גבוה יותר מסוכן מrecall נמוך?

עברתם 4 מתוך 5? מצוין -- אתם מוכנים לפרק 19.

סיכום הפרק

בפרק הזה בניתם מערכת DevOps Agent מלאה. התחלתם עם Architecture -- Event Router ל-4 סוכנים מתמחים. בניתם Code Review Agent שמנתח 5 קטגוריות ומפרסם inline comments. הוספתם Security Scanner עם 4 שכבות: secrets, vulnerabilities, dependencies, config. בניתם Test Generation Agent עם pytest/Jest. יצרתם Deployment Assistant עם pre-deploy checklist ו-anomaly detection. בניתם Production Monitor עם incident analysis ו-runbooks. שילבתם ב-CI/CD עם GitHub Actions ו-webhook server. הוספתם Context system שלומד מ-history ו-feedback. בניתם Evaluation framework עם 4 מדדים.

הנקודה המרכזית: DevOps Agent טוב מוצא את ה-issues הנכונים, בצורה שמפתחים סומכים עליה. Precision > Recall. Quality > Quantity. 5 הערות מדויקות שווות יותר מ-50 עם 30% FPs.

בפרק 19 תעבירו את כל הסוכנים לפרודקשן -- hosting, monitoring, scaling, cost optimization.

צ'קליסט -- סיכום פרק 18