11"""
22Core agent execution logic shared between sync and async endpoints.
3+
4+ Supports both single-command execution and full workflow orchestration.
35"""
46
57import json
68import logging
9+ import os
710import traceback
11+ import uuid
812from collections .abc import Callable
913from dataclasses import dataclass , field
1014from pathlib import Path
2226
2327# Resolve the plugin directory (repo root) relative to this file.
2428PLUGIN_DIR = str (Path (__file__ ).resolve ().parent .parent / "plugins" / "oape" )
29+ TEAM_REPOS_CSV = str (Path (__file__ ).resolve ().parent .parent / "team-repos.csv" )
2530
2631CONVERSATION_LOG = Path ("/tmp/conversation.log" )
2732
3439with open (Path (__file__ ).resolve ().parent / "config.json" ) as cf :
3540 CONFIGS = json .loads (cf .read ())
3641
37- # Supported commands and their corresponding plugin skill names.
38- SUPPORTED_COMMANDS = {
39- "api-implement" : "oape:api-implement" ,
40- }
42+ # ---------------------------------------------------------------------------
43+ # Workflow system prompt — instructs a single long-running agent session
44+ # to execute the full feature development pipeline.
45+ # ---------------------------------------------------------------------------
46+
47+ WORKFLOW_SYSTEM_PROMPT = """\
48+ You are an OpenShift operator AI feature development agent. Your job is to
49+ execute a complete feature development workflow given an Enhancement Proposal
50+ (EP) PR URL. You MUST follow every phase below **in order**, and you MUST NOT
51+ skip any phase. Stream your progress clearly so the user can follow along.
52+
53+ ## Inputs
54+ - EP_URL: the GitHub Enhancement Proposal PR URL (provided in the user prompt).
55+
56+ ## Allowed repositories
57+ Read the file {team_repos_csv} to get the list of allowed repositories and
58+ their base branches (CSV columns: product, role, repo_url, base_branch).
59+
60+ ## Phase 0 — Detect target repository
61+ 1. Fetch the EP PR description using `gh pr view <EP_URL> --json body -q .body`
62+ (or WebFetch the PR page).
63+ 2. From the EP content, determine which operator repository it targets.
64+ Match against the repos in {team_repos_csv}.
65+ 3. Extract: REPO_SHORT_NAME, REPO_URL, BASE_BRANCH.
66+ - REPO_SHORT_NAME is the last path component of the repo URL without .git
67+ (e.g. "cert-manager-operator").
68+ 4. Print a summary: "Detected repo: <REPO_SHORT_NAME>, base branch: <BASE_BRANCH>".
69+
70+ If you cannot determine the repo, STOP and report the failure.
71+
72+ ## Phase 1 — Clone repository
73+ 1. Run `/oape:init <REPO_SHORT_NAME>` in the current working directory.
74+ 2. After init completes, `cd` into the cloned repo directory.
75+ 3. Run `git fetch origin` and `git checkout <BASE_BRANCH>` to ensure you are
76+ on the correct base branch.
77+ 4. Extract the EP number from EP_URL (the numeric part after /pull/).
78+
79+ ## Phase 2 — PR #1: API Types + Tests
80+ 1. Create and checkout a new branch: `git checkout -b oape/api-types-<EP_NUMBER>`
81+ 2. Run `/oape:api-generate <EP_URL>`.
82+ 3. Identify the generated API directory (typically `api/` or a subdirectory).
83+ 4. Run `/oape:api-generate-tests <path-to-api-directory>`.
84+ 5. Run `make generate && make manifests` (if Makefile targets exist).
85+ 6. Run `make build && make test` to verify the code compiles and tests pass.
86+ If build or tests fail, attempt to fix the issues.
87+ 7. Run `/oape:review NO_TICKET origin/<BASE_BRANCH>` to review code quality.
88+ The review will auto-fix issues it finds.
89+ 8. Run `make build && make test` again after review fixes.
90+ 9. Stage all changes: `git add -A`
91+ 10. Commit: `git commit -m "oape: generate API types and tests from EP #<EP_NUMBER>"`
92+ 11. Push: `git push -u origin oape/api-types-<EP_NUMBER>`
93+ 12. Create PR #1:
94+ ```
95+ gh pr create \\
96+ --base <BASE_BRANCH> \\
97+ --title "oape: API types and tests from EP #<EP_NUMBER>" \\
98+ --body "Auto-generated API type definitions and integration tests from <EP_URL>"
99+ ```
100+ 13. Save the PR #1 URL.
101+
102+ ## Phase 3 — PR #2: Controller Implementation
103+ 1. From the current branch (oape/api-types-<EP_NUMBER>), create a new branch:
104+ `git checkout -b oape/controller-<EP_NUMBER>`
105+ 2. Run `/oape:api-implement <EP_URL>`.
106+ 3. Run `make generate && make manifests` (if targets exist).
107+ 4. Run `make build && make test`. Fix failures if any.
108+ 5. Run `/oape:review NO_TICKET origin/<BASE_BRANCH>` to review.
109+ 6. Run `make build && make test` again after review fixes.
110+ 7. Stage, commit: `git commit -m "oape: implement controller from EP #<EP_NUMBER>"`
111+ 8. Push: `git push -u origin oape/controller-<EP_NUMBER>`
112+ 9. Create PR #2 (base = the api-types branch so it stacks):
113+ ```
114+ gh pr create \\
115+ --base oape/api-types-<EP_NUMBER> \\
116+ --title "oape: controller implementation from EP #<EP_NUMBER>" \\
117+ --body "Auto-generated controller/reconciler code from <EP_URL>"
118+ ```
119+ 10. Save the PR #2 URL.
120+
121+ ## Phase 4 — PR #3: E2E Tests
122+ 1. Go back to the api-types branch: `git checkout oape/api-types-<EP_NUMBER>`
123+ 2. Create a new branch: `git checkout -b oape/e2e-<EP_NUMBER>`
124+ 3. Run `/oape:e2e-generate <BASE_BRANCH>`.
125+ 4. Run `/oape:review NO_TICKET origin/<BASE_BRANCH>` to review.
126+ 5. Fix any issues, verify build passes.
127+ 6. Stage, commit: `git commit -m "oape: generate e2e tests from EP #<EP_NUMBER>"`
128+ 7. Push: `git push -u origin oape/e2e-<EP_NUMBER>`
129+ 8. Create PR #3 (base = the api-types branch):
130+ ```
131+ gh pr create \\
132+ --base oape/api-types-<EP_NUMBER> \\
133+ --title "oape: e2e tests from EP #<EP_NUMBER>" \\
134+ --body "Auto-generated e2e test artifacts from <EP_URL>"
135+ ```
136+ 9. Save the PR #3 URL.
137+
138+ ## Phase 5 — Summary
139+ Output a final summary in this exact format:
140+
141+ ```
142+ === OAPE Workflow Complete ===
143+
144+ Enhancement Proposal: <EP_URL>
145+ Target Repository: <REPO_SHORT_NAME>
146+ Base Branch: <BASE_BRANCH>
147+
148+ PR #1 (API Types + Tests): <PR_1_URL>
149+ PR #2 (Controller): <PR_2_URL>
150+ PR #3 (E2E Tests): <PR_3_URL>
151+ ```
152+
153+ ## Critical Rules
154+ - NEVER skip a phase. Execute them in order.
155+ - If a phase fails and you cannot recover, STOP and report which phase failed and why.
156+ - Always use `git add -A` before committing to include all generated files.
157+ - The review command with NO_TICKET skips Jira validation and reviews code quality only.
158+ - Use `gh pr create` (not manual URL construction) for creating PRs.
159+ - Do NOT modify the EP or any upstream repository.
160+ """
41161
42162
43163@dataclass
@@ -55,37 +175,25 @@ def success(self) -> bool:
55175
56176
57177async def run_agent (
58- command : str ,
59- ep_url : str ,
178+ prompt : str ,
60179 working_dir : str ,
180+ system_prompt : str ,
61181 on_message : Callable [[dict ], None ] | None = None ,
62182) -> AgentResult :
63- """Run the Claude agent and return the result .
183+ """Run the Claude agent with an arbitrary prompt and system prompt .
64184
65185 Args:
66- command : The command key (e.g. "api-implement") .
67- ep_url: The enhancement proposal PR URL .
68- working_dir: Absolute path to the operator repo .
186+ prompt : The user prompt to send to the agent .
187+ working_dir: Absolute path to the working directory .
188+ system_prompt: The system prompt for the agent .
69189 on_message: Optional callback invoked with each conversation message
70190 dict as it arrives, enabling real-time streaming.
71191
72192 Returns:
73193 An AgentResult with the output or error.
74194 """
75- skill_name = SUPPORTED_COMMANDS .get (command )
76- if skill_name is None :
77- return AgentResult (
78- output = "" ,
79- cost_usd = 0.0 ,
80- error = f"Unsupported command: { command } . "
81- f"Supported: { ', ' .join (SUPPORTED_COMMANDS )} " ,
82- )
83-
84195 options = ClaudeAgentOptions (
85- system_prompt = (
86- "You are an OpenShift operator code generation assistant. "
87- f"Execute the { skill_name } plugin with the provided EP URL. "
88- ),
196+ system_prompt = system_prompt ,
89197 cwd = working_dir ,
90198 permission_mode = "bypassPermissions" ,
91199 allowed_tools = CONFIGS ["claude_allowed_tools" ],
@@ -97,7 +205,7 @@ async def run_agent(
97205 cost_usd = 0.0
98206
99207 conv_logger .info (
100- f"\n { '=' * 60 } \n [request] command= { command } ep_url= { ep_url } "
208+ f"\n { '=' * 60 } \n [request] prompt= { prompt [: 120 ] } "
101209 f"cwd={ working_dir } \n { '=' * 60 } "
102210 )
103211
@@ -109,7 +217,7 @@ def _emit(entry: dict) -> None:
109217
110218 try :
111219 async for message in query (
112- prompt = f"/ { skill_name } { ep_url } " ,
220+ prompt = prompt ,
113221 options = options ,
114222 ):
115223 if isinstance (message , AssistantMessage ):
@@ -198,3 +306,39 @@ def _emit(entry: dict) -> None:
198306 error = str (exc ),
199307 conversation = conversation ,
200308 )
309+
310+
311+ async def run_workflow (
312+ ep_url : str ,
313+ on_message : Callable [[dict ], None ] | None = None ,
314+ ) -> AgentResult :
315+ """Run the full OAPE feature development workflow.
316+
317+ Creates a temp directory, then launches a single long-running agent session
318+ that executes all phases: init, api-generate, api-generate-tests, review,
319+ PR creation, api-implement, e2e-generate, etc.
320+
321+ Args:
322+ ep_url: The enhancement proposal PR URL.
323+ on_message: Optional streaming callback.
324+
325+ Returns:
326+ An AgentResult with the final summary or error.
327+ """
328+ job_id = uuid .uuid4 ().hex [:12 ]
329+ working_dir = f"/tmp/oape-{ job_id } "
330+ os .makedirs (working_dir , exist_ok = True )
331+
332+ system_prompt = WORKFLOW_SYSTEM_PROMPT .format (
333+ team_repos_csv = TEAM_REPOS_CSV ,
334+ )
335+
336+ return await run_agent (
337+ prompt = (
338+ f"Execute the full OAPE feature development workflow for this "
339+ f"Enhancement Proposal: { ep_url } "
340+ ),
341+ working_dir = working_dir ,
342+ system_prompt = system_prompt ,
343+ on_message = on_message ,
344+ )
0 commit comments