Skip to content

Commit a4e5a05

Browse files
swghoshclaude
andcommitted
Add workflow based agent
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 31134a5 commit a4e5a05

11 files changed

Lines changed: 770 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
__pycache__
22
.claude
3+
.oape-work/

agent-wo-server/__init__.py

Whitespace-only changes.

agent-wo-server/agent/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""OAPE AI E2E Workflow Orchestrator - Agent SDK based pipeline."""

agent-wo-server/agent/config.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Constants and ClaudeAgentOptions factory."""
2+
3+
from pathlib import Path
4+
5+
from claude_agent_sdk import ClaudeAgentOptions
6+
7+
# ---------------------------------------------------------------------------
8+
# Paths (resolved relative to project root, two levels up from this file)
9+
# ---------------------------------------------------------------------------
10+
11+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
12+
OAPE_PLUGIN_PATH = PROJECT_ROOT / "plugins" / "oape"
13+
TEAM_REPOS_CSV = PROJECT_ROOT / "team-repos.csv"
14+
15+
# ---------------------------------------------------------------------------
16+
# Defaults (can be overridden via CLI flags)
17+
# ---------------------------------------------------------------------------
18+
19+
POLL_INTERVAL_SECS = 60
20+
MAX_CI_WAIT_MINS = 120
21+
MAX_AGENT_TURNS = 200
22+
23+
# ---------------------------------------------------------------------------
24+
# Agent options factory
25+
# ---------------------------------------------------------------------------
26+
27+
28+
def make_agent_options(
29+
cwd: str,
30+
system_prompt_append: str = "",
31+
max_turns: int | None = None,
32+
) -> ClaudeAgentOptions:
33+
"""Create ClaudeAgentOptions with the oape plugin loaded and full tool access."""
34+
return ClaudeAgentOptions(
35+
system_prompt={
36+
"type": "preset",
37+
"preset": "claude_code",
38+
"append": system_prompt_append,
39+
},
40+
permission_mode="bypassPermissions",
41+
cwd=cwd,
42+
setting_sources=["project"],
43+
plugins=[{"type": "local", "path": str(OAPE_PLUGIN_PATH)}],
44+
allowed_tools=[
45+
"Bash",
46+
"Read",
47+
"Write",
48+
"Edit",
49+
"Glob",
50+
"Grep",
51+
"WebFetch",
52+
"WebSearch",
53+
"Skill",
54+
"Task",
55+
],
56+
max_turns=max_turns or MAX_AGENT_TURNS,
57+
)

agent-wo-server/agent/main.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env python3
2+
"""
3+
OAPE AI E2E Workflow — entry point.
4+
5+
Usage:
6+
python -m src.agent.main
7+
8+
You will be prompted for the Enhancement Proposal URL and target repository.
9+
"""
10+
11+
import asyncio
12+
import re
13+
import sys
14+
15+
from . import phase1, phase2, phase3
16+
from .config import MAX_CI_WAIT_MINS, POLL_INTERVAL_SECS
17+
from .state import WorkflowState
18+
from .utils import resolve_repo
19+
20+
21+
def _prompt_ep_url() -> str:
22+
"""Interactively ask for the Enhancement Proposal URL."""
23+
print("=" * 70)
24+
print("OAPE AI E2E Workflow Orchestrator")
25+
print("=" * 70)
26+
print()
27+
28+
url = input("Enhancement Proposal PR URL: ").strip()
29+
30+
if not re.match(
31+
r"^https://github\.com/openshift/enhancements/pull/\d+/?$", url
32+
):
33+
print(
34+
"ERROR: URL must match "
35+
"https://github.com/openshift/enhancements/pull/<number>"
36+
)
37+
sys.exit(1)
38+
39+
return url
40+
41+
42+
async def run_workflow() -> None:
43+
"""Full orchestration: prompt for inputs, run all phases."""
44+
ep_url = _prompt_ep_url()
45+
46+
repo_short, repo_url, base_branch = resolve_repo(ep_url)
47+
48+
state = WorkflowState(
49+
ep_url=ep_url,
50+
repo_short_name=repo_short,
51+
repo_url=repo_url,
52+
base_branch=base_branch,
53+
)
54+
55+
print(f"\nTarget: {repo_short} ({repo_url})")
56+
print(f"Base branch: {base_branch}")
57+
58+
# Phase 1 — sequential: init -> api-generate -> api-generate-tests -> fix -> PR
59+
await phase1.run(state)
60+
61+
if not state.repo_local_path:
62+
print("ERROR: Phase 1 failed to establish repo local path. Aborting.")
63+
sys.exit(1)
64+
65+
# Phase 2 — parallel sub-agents: controller + e2e tests
66+
await phase2.run(state)
67+
68+
# Phase 3 — watch PRs for CI green
69+
await phase3.run(state)
70+
71+
72+
def main() -> None:
73+
asyncio.run(run_workflow())
74+
75+
76+
if __name__ == "__main__":
77+
main()

agent-wo-server/agent/phase1.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""
2+
Phase 1: Sequential API Types Pipeline
3+
4+
init -> api-generate -> api-generate-tests -> review-and-fix -> raise PR
5+
"""
6+
7+
import textwrap
8+
import time
9+
10+
from claude_agent_sdk import ClaudeSDKClient
11+
12+
from .config import make_agent_options
13+
from .state import WorkflowState, make_workdir, write_state_summary
14+
from .utils import collect_response, extract_pr_url, extract_text
15+
16+
17+
async def run(state: WorkflowState) -> None:
18+
"""Execute the full Phase 1 pipeline inside a single ClaudeSDKClient session."""
19+
workdir = make_workdir("phase1-api-types")
20+
21+
print("\n" + "=" * 70)
22+
print("PHASE 1: API Types Pipeline")
23+
print("=" * 70)
24+
25+
opts = make_agent_options(
26+
cwd=str(workdir),
27+
system_prompt_append=textwrap.dedent(f"""\
28+
You are running an automated OAPE workflow.
29+
The Enhancement Proposal URL is: {state.ep_url}
30+
The target repository short name is: {state.repo_short_name}
31+
The target repository URL is: {state.repo_url}
32+
The base branch is: {state.base_branch}
33+
34+
You will execute a series of /oape: slash commands in sequence.
35+
After each command completes, proceed to the next.
36+
Do NOT ask the user for confirmation between steps -- proceed autonomously.
37+
If a step fails, report the error and stop.
38+
39+
At the end, write a summary of everything that happened to a file
40+
called 'api-types-summary.md' in the current working directory.
41+
"""),
42+
)
43+
44+
async with ClaudeSDKClient(opts) as client:
45+
# -- Step 1: Clone repo --
46+
print("\n--- Step 1/5: /oape:init ---")
47+
await client.query(f"/oape:init {state.repo_short_name}")
48+
await collect_response(client)
49+
50+
# Determine the cloned repo path
51+
cloned_dir = workdir / state.repo_short_name
52+
if cloned_dir.is_dir():
53+
state.repo_local_path = str(cloned_dir)
54+
else:
55+
for child in workdir.iterdir():
56+
if (child / ".git").is_dir():
57+
state.repo_local_path = str(child)
58+
break
59+
60+
# -- Step 2: Generate API types --
61+
print("\n--- Step 2/5: /oape:api-generate ---")
62+
await client.query(
63+
f"cd {state.repo_local_path} && /oape:api-generate {state.ep_url}"
64+
)
65+
msgs = await collect_response(client)
66+
api_gen_text = extract_text(msgs)
67+
68+
# -- Step 3: Generate API tests --
69+
print("\n--- Step 3/5: /oape:api-generate-tests ---")
70+
await client.query(
71+
"Now run /oape:api-generate-tests on the API types directory you just "
72+
"generated. Determine the correct path from the api-generate output above."
73+
)
74+
msgs = await collect_response(client)
75+
api_tests_text = extract_text(msgs)
76+
77+
# -- Step 4: Review and fix --
78+
print("\n--- Step 4/5: Review and fix ---")
79+
await client.query(
80+
"Now review the changes you've made. Run `go build ./...` and `go vet ./...` "
81+
"to check for compilation errors. Fix any issues found. "
82+
"If `make generate` or `make manifests` targets exist, run them. "
83+
"Ensure the code compiles cleanly."
84+
)
85+
await collect_response(client)
86+
87+
# -- Step 5: Commit and raise PR --
88+
print("\n--- Step 5/5: Raise PR ---")
89+
branch_name = f"oape/api-types-{int(time.time())}"
90+
await client.query(
91+
textwrap.dedent(f"""\
92+
Now commit all changes and raise a PR:
93+
1. Create and checkout a new branch: {branch_name}
94+
2. Stage all changed/new files (except .oape-work/)
95+
3. Commit with a descriptive conventional commit message
96+
4. Push the branch to origin
97+
5. Create a PR against '{state.base_branch}' using `gh pr create`
98+
with a clear title and body describing the API types from the EP.
99+
6. Print the PR URL at the end.
100+
""")
101+
)
102+
msgs = await collect_response(client)
103+
pr_url = extract_pr_url(extract_text(msgs))
104+
if pr_url:
105+
state.pr_urls["api-types"] = pr_url
106+
print(f"\n >> API Types PR: {pr_url}")
107+
108+
# -- Write state summary for downstream phases --
109+
summary = textwrap.dedent(f"""\
110+
# Phase 1: API Types Summary
111+
112+
## Enhancement Proposal
113+
{state.ep_url}
114+
115+
## Repository
116+
- Short name: {state.repo_short_name}
117+
- URL: {state.repo_url}
118+
- Base branch: {state.base_branch}
119+
- Local path: {state.repo_local_path}
120+
121+
## Steps Completed
122+
1. Repository cloned via /oape:init
123+
2. API types generated via /oape:api-generate
124+
3. API integration tests generated via /oape:api-generate-tests
125+
4. Code reviewed and fixed (build + vet)
126+
5. PR raised: {state.pr_urls.get('api-types', 'N/A')}
127+
128+
## API Generation Output (excerpt)
129+
{api_gen_text[:2000]}
130+
131+
## API Test Generation Output (excerpt)
132+
{api_tests_text[:2000]}
133+
""")
134+
state.api_summary_md = str(
135+
write_state_summary(workdir, "api-types-summary.md", summary)
136+
)
137+
print(f" State summary: {state.api_summary_md}")

0 commit comments

Comments
 (0)