Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions plugins/oape/commands/review.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ The review covers four key modules:

## Arguments

- `$1` (ticket_id): The Jira Ticket ID (e.g., OCPBUGS-12345). **Required.**
- `$1` (ticket_id_or_NO_TICKET): The Jira Ticket ID (e.g., OCPBUGS-12345) **OR** the literal string `NO_TICKET`. **Required.**
- When `NO_TICKET` is provided, the review skips Jira validation and focuses on code quality, safety, and build consistency only.
- `$2` (base_ref): The base git ref to diff against. Defaults to `origin/master`. **Optional.**


Expand All @@ -38,11 +39,16 @@ BASE_REF="${2:-origin/master}"
```

### Step 2: Fetch Context
1. **Jira Issue**: Fetch the Jira issue details using curl:
```bash
curl -s "https://issues.redhat.com/browse/$1"
```
Focus on Acceptance Criteria as the primary validation source.
1. **Jira Issue** (skip if `$1` is `NO_TICKET`):
- If `$1` is NOT `NO_TICKET`, fetch the Jira issue details using curl:
```bash
curl -s "https://issues.redhat.com/browse/$1"
```
Focus on Acceptance Criteria as the primary validation source.
- If `$1` IS `NO_TICKET`, skip Jira fetching entirely. The review will focus
on code quality (Modules A-D) without validating against Jira acceptance criteria.
In the Logic Verification module, skip the "Intent Match" check that compares
code against Jira requirements.

2. **Git Diff**: Get the code changes:
```bash
Expand All @@ -61,7 +67,7 @@ Apply the following review criteria:
#### Module A: Golang (Logic & Safety)

**Logic Verification (The "Mental Sandbox")**:
- **Intent Match:** Does the code implementation match the Jira Acceptance Criteria? Quote the Jira line that justifies the change.
- **Intent Match:** (Skip if `NO_TICKET` mode) Does the code implementation match the Jira Acceptance Criteria? Quote the Jira line that justifies the change. In `NO_TICKET` mode, verify that the code changes are internally consistent and logically correct.
- **Execution Trace:** Mentally simulate the function.
- *Happy Path:* Does it succeed as expected?
- *Error Path:* If the API fails, does it retry or return an error?
Expand Down Expand Up @@ -116,7 +122,7 @@ Returns a JSON report with the following structure, followed by an automatic fix
"simplicity_score": "1-10"
},
"logic_verification": {
"jira_intent_met": true,
"jira_intent_met": true, // Set to null in NO_TICKET mode
"missing_edge_cases": ["List handled edge cases or gaps (e.g., 'Does not handle pod deletion')"]
},
"issues": [
Expand Down
194 changes: 169 additions & 25 deletions server/agent.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
"""
Core agent execution logic shared between sync and async endpoints.

Supports both single-command execution and full workflow orchestration.
"""

import json
import logging
import os
import traceback
import uuid
from collections.abc import Callable
from dataclasses import dataclass, field
from pathlib import Path
Expand All @@ -22,6 +26,7 @@

# Resolve the plugin directory (repo root) relative to this file.
PLUGIN_DIR = str(Path(__file__).resolve().parent.parent / "plugins" / "oape")
TEAM_REPOS_CSV = str(Path(__file__).resolve().parent.parent / "team-repos.csv")

CONVERSATION_LOG = Path("/tmp/conversation.log")

Expand All @@ -34,10 +39,125 @@
with open(Path(__file__).resolve().parent / "config.json") as cf:
CONFIGS = json.loads(cf.read())

# Supported commands and their corresponding plugin skill names.
SUPPORTED_COMMANDS = {
"api-implement": "oape:api-implement",
}
# ---------------------------------------------------------------------------
# Workflow system prompt — instructs a single long-running agent session
# to execute the full feature development pipeline.
# ---------------------------------------------------------------------------

WORKFLOW_SYSTEM_PROMPT = """\
You are an OpenShift operator AI feature development agent. Your job is to
execute a complete feature development workflow given an Enhancement Proposal
(EP) PR URL. You MUST follow every phase below **in order**, and you MUST NOT
skip any phase. Stream your progress clearly so the user can follow along.

## Inputs
- EP_URL: the GitHub Enhancement Proposal PR URL (provided in the user prompt).

## Allowed repositories
Read the file {team_repos_csv} to get the list of allowed repositories and
their base branches (CSV columns: product, role, repo_url, base_branch).

## Phase 0 — Detect target repository
1. Fetch the EP PR description using `gh pr view <EP_URL> --json body -q .body`
(or WebFetch the PR page).
2. From the EP content, determine which operator repository it targets.
Match against the repos in {team_repos_csv}.
3. Extract: REPO_SHORT_NAME, REPO_URL, BASE_BRANCH.
- REPO_SHORT_NAME is the last path component of the repo URL without .git
(e.g. "cert-manager-operator").
4. Print a summary: "Detected repo: <REPO_SHORT_NAME>, base branch: <BASE_BRANCH>".

If you cannot determine the repo, STOP and report the failure.

## Phase 1 — Clone repository
1. Run `/oape:init <REPO_SHORT_NAME>` in the current working directory.
2. After init completes, `cd` into the cloned repo directory.
3. Run `git fetch origin` and `git checkout <BASE_BRANCH>` to ensure you are
on the correct base branch.
4. Extract the EP number from EP_URL (the numeric part after /pull/).

## Phase 2 — PR #1: API Types + Tests
1. Create and checkout a new branch: `git checkout -b oape/api-types-<EP_NUMBER>`
2. Run `/oape:api-generate <EP_URL>`.
3. Identify the generated API directory (typically `api/` or a subdirectory).
4. Run `/oape:api-generate-tests <path-to-api-directory>`.
5. Run `make generate && make manifests` (if Makefile targets exist).
6. Run `make build && make test` to verify the code compiles and tests pass.
If build or tests fail, attempt to fix the issues.
7. Run `/oape:review NO_TICKET origin/<BASE_BRANCH>` to review code quality.
The review will auto-fix issues it finds.
8. Run `make build && make test` again after review fixes.
9. Stage all changes: `git add -A`
10. Commit: `git commit -m "oape: generate API types and tests from EP #<EP_NUMBER>"`
11. Push: `git push -u origin oape/api-types-<EP_NUMBER>`
12. Create PR #1:
```
gh pr create \\
--base <BASE_BRANCH> \\
--title "oape: API types and tests from EP #<EP_NUMBER>" \\
--body "Auto-generated API type definitions and integration tests from <EP_URL>"
```
13. Save the PR #1 URL.

## Phase 3 — PR #2: Controller Implementation
1. From the current branch (oape/api-types-<EP_NUMBER>), create a new branch:
`git checkout -b oape/controller-<EP_NUMBER>`
2. Run `/oape:api-implement <EP_URL>`.
3. Run `make generate && make manifests` (if targets exist).
4. Run `make build && make test`. Fix failures if any.
5. Run `/oape:review NO_TICKET origin/<BASE_BRANCH>` to review.
6. Run `make build && make test` again after review fixes.
7. Stage, commit: `git commit -m "oape: implement controller from EP #<EP_NUMBER>"`
8. Push: `git push -u origin oape/controller-<EP_NUMBER>`
9. Create PR #2 (base = the api-types branch so it stacks):
```
gh pr create \\
--base oape/api-types-<EP_NUMBER> \\
--title "oape: controller implementation from EP #<EP_NUMBER>" \\
--body "Auto-generated controller/reconciler code from <EP_URL>"
```
10. Save the PR #2 URL.

## Phase 4 — PR #3: E2E Tests
1. Go back to the api-types branch: `git checkout oape/api-types-<EP_NUMBER>`
2. Create a new branch: `git checkout -b oape/e2e-<EP_NUMBER>`
3. Run `/oape:e2e-generate <BASE_BRANCH>`.
4. Run `/oape:review NO_TICKET origin/<BASE_BRANCH>` to review.
5. Fix any issues, verify build passes.
6. Stage, commit: `git commit -m "oape: generate e2e tests from EP #<EP_NUMBER>"`
7. Push: `git push -u origin oape/e2e-<EP_NUMBER>`
8. Create PR #3 (base = the api-types branch):
```
gh pr create \\
--base oape/api-types-<EP_NUMBER> \\
--title "oape: e2e tests from EP #<EP_NUMBER>" \\
--body "Auto-generated e2e test artifacts from <EP_URL>"
```
9. Save the PR #3 URL.

## Phase 5 — Summary
Output a final summary in this exact format:

```
=== OAPE Workflow Complete ===

Enhancement Proposal: <EP_URL>
Target Repository: <REPO_SHORT_NAME>
Base Branch: <BASE_BRANCH>

PR #1 (API Types + Tests): <PR_1_URL>
PR #2 (Controller): <PR_2_URL>
PR #3 (E2E Tests): <PR_3_URL>
```

## Critical Rules
- NEVER skip a phase. Execute them in order.
- If a phase fails and you cannot recover, STOP and report which phase failed and why.
- Always use `git add -A` before committing to include all generated files.
- The review command with NO_TICKET skips Jira validation and reviews code quality only.
- Use `gh pr create` (not manual URL construction) for creating PRs.
- Do NOT modify the EP or any upstream repository.
"""


@dataclass
Expand All @@ -55,37 +175,25 @@ def success(self) -> bool:


async def run_agent(
command: str,
ep_url: str,
prompt: str,
working_dir: str,
system_prompt: str,
on_message: Callable[[dict], None] | None = None,
) -> AgentResult:
"""Run the Claude agent and return the result.
"""Run the Claude agent with an arbitrary prompt and system prompt.

Args:
command: The command key (e.g. "api-implement").
ep_url: The enhancement proposal PR URL.
working_dir: Absolute path to the operator repo.
prompt: The user prompt to send to the agent.
working_dir: Absolute path to the working directory.
system_prompt: The system prompt for the agent.
on_message: Optional callback invoked with each conversation message
dict as it arrives, enabling real-time streaming.

Returns:
An AgentResult with the output or error.
"""
skill_name = SUPPORTED_COMMANDS.get(command)
if skill_name is None:
return AgentResult(
output="",
cost_usd=0.0,
error=f"Unsupported command: {command}. "
f"Supported: {', '.join(SUPPORTED_COMMANDS)}",
)

options = ClaudeAgentOptions(
system_prompt=(
"You are an OpenShift operator code generation assistant. "
f"Execute the {skill_name} plugin with the provided EP URL. "
),
system_prompt=system_prompt,
cwd=working_dir,
permission_mode="bypassPermissions",
allowed_tools=CONFIGS["claude_allowed_tools"],
Expand All @@ -97,7 +205,7 @@ async def run_agent(
cost_usd = 0.0

conv_logger.info(
f"\n{'=' * 60}\n[request] command={command} ep_url={ep_url} "
f"\n{'=' * 60}\n[request] prompt={prompt[:120]} "
f"cwd={working_dir}\n{'=' * 60}"
)

Expand All @@ -109,7 +217,7 @@ def _emit(entry: dict) -> None:

try:
async for message in query(
prompt=f"/{skill_name} {ep_url}",
prompt=prompt,
options=options,
):
if isinstance(message, AssistantMessage):
Expand Down Expand Up @@ -198,3 +306,39 @@ def _emit(entry: dict) -> None:
error=str(exc),
conversation=conversation,
)


async def run_workflow(
ep_url: str,
on_message: Callable[[dict], None] | None = None,
) -> AgentResult:
"""Run the full OAPE feature development workflow.

Creates a temp directory, then launches a single long-running agent session
that executes all phases: init, api-generate, api-generate-tests, review,
PR creation, api-implement, e2e-generate, etc.

Args:
ep_url: The enhancement proposal PR URL.
on_message: Optional streaming callback.

Returns:
An AgentResult with the final summary or error.
"""
job_id = uuid.uuid4().hex[:12]
working_dir = f"/tmp/oape-{job_id}"
os.makedirs(working_dir, exist_ok=True)

system_prompt = WORKFLOW_SYSTEM_PROMPT.format(
team_repos_csv=TEAM_REPOS_CSV,
)

return await run_agent(
prompt=(
f"Execute the full OAPE feature development workflow for this "
f"Enhancement Proposal: {ep_url}"
),
working_dir=working_dir,
system_prompt=system_prompt,
on_message=on_message,
)
Loading