diff --git a/ci-operator/step-registry/hypershift/jira-agent/README.md b/ci-operator/step-registry/hypershift/jira-agent/README.md deleted file mode 100644 index 0630d7c03d8f3..0000000000000 --- a/ci-operator/step-registry/hypershift/jira-agent/README.md +++ /dev/null @@ -1,286 +0,0 @@ -# HyperShift Jira Agent Workflow - -Automated periodic job that processes Jira issues labeled with `issue-for-agent` and creates pull requests using Claude Code. - -## Overview - -This workflow implements a fully automated system for processing HyperShift Jira issues: - -1. **Query**: Searches Jira for unresolved issues in OCPBUGS and CNTRLPLANE projects with label `issue-for-agent` (excluding those with `agent-processed`) -2. **Process**: For each issue, runs the `/jira-solve` command from the HyperShift repository non-interactively -3. **Track**: Adds `agent-processed` label to successfully processed issues to prevent reprocessing - -## Data Flow Diagram - -```mermaid -flowchart TD - %% Trigger - Start([Cron Trigger
Daily 9:00 AM UTC]):::trigger --> PrePhase - - %% PRE-PHASE: Setup - subgraph PrePhase[PRE-PHASE: Setup] - direction TB - Verify[Verify Claude Code CLI
claude --version]:::setup - end - - %% TEST-PHASE: Process - PrePhase --> TestPhase - - subgraph TestPhase[TEST-PHASE: Process Issues] - direction TB - - CloneRepos[Clone Repositories
ai-helpers + hypershift-community/hypershift]:::setup - CopyCommand[Copy jira-solve command
to .claude/commands/]:::setup - GitConfig[Configure Git
user: OpenShift CI Bot]:::setup - GenTokens[Generate GitHub App Tokens
JWT auth for fork + upstream]:::setup - - QueryJira[Query Jira API
JQL: status in New, To Do
AND labels = issue-for-agent
AND labels != agent-processed]:::process - - CheckIssues{Issues
Found?}:::decision - CheckMax{Processed <
MAX_ISSUES
Default: 1}:::decision - CheckSuccess{Processing
Successful?}:::decision - - ProcessIssue[Run Claude Code CLI
--system-prompt jira-solve.md
--max-turns 100]:::ai - - AddLabel[Add label
agent-processed
to Jira issue]:::success - LogFailure[Log failure
Will retry next run]:::failure - NoIssues[Exit: No issues to process]:::skip - - RateLimit[Wait 60 seconds
Rate limiting]:::process - Summary[Print Summary
Processed/Failed counts]:::process - - CloneRepos --> CopyCommand --> GitConfig --> GenTokens --> QueryJira - QueryJira --> CheckIssues - CheckIssues -->|No| NoIssues - CheckIssues -->|Yes| CheckMax - CheckMax -->|No| Summary - CheckMax -->|Yes| ProcessIssue - ProcessIssue --> CheckSuccess - CheckSuccess -->|Yes| AddLabel - CheckSuccess -->|No| LogFailure - AddLabel --> RateLimit - LogFailure --> RateLimit - RateLimit --> CheckMax - end - - %% Secrets - Secret1[(Secret:
hypershift-team-claude-prow
app-id, private-key,
installation-ids)]:::secret -.->|GitHub App auth| GenTokens - Secret1 -.->|Vertex AI auth| ProcessIssue - - %% External Systems - JiraAPI[(Jira API
redhat.atlassian.net)]:::external -.->|Return issues| QueryJira - JiraAPI -.->|Add label| AddLabel - ClaudeAPI[(Claude API
via Vertex AI)]:::external -.->|Generate solution| ProcessIssue - GitHubAPI[(GitHub API)]:::external -.->|Push to fork| ProcessIssue - GitHubAPI -.->|Create PR to upstream| ProcessIssue - - TestPhase --> End([Workflow Complete]):::trigger - NoIssues --> End - Summary --> End - - %% Style Definitions - classDef trigger fill:#e1f5ff,stroke:#01579b,stroke-width:3px,color:#000 - classDef setup fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#000 - classDef process fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px,color:#000 - classDef decision fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000 - classDef ai fill:#fce4ec,stroke:#880e4f,stroke-width:3px,color:#000 - classDef success fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px,color:#000 - classDef failure fill:#ffcdd2,stroke:#c62828,stroke-width:2px,color:#000 - classDef skip fill:#f5f5f5,stroke:#757575,stroke-width:1px,color:#000 - classDef external fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000 - classDef secret fill:#ffebee,stroke:#b71c1c,stroke-width:2px,color:#000 -``` - -## Components - -### Workflow -- **File**: `hypershift-jira-agent-workflow.yaml` -- **Description**: Defines the two-phase workflow (pre/test) - -### Steps - -#### 1. Setup (`hypershift-jira-agent-setup`) -- Verifies Claude Code CLI is available - -#### 2. Process (`hypershift-jira-agent-process`) -- Clones ai-helpers and hypershift-community/hypershift repositories -- Copies jira-solve command to `.claude/commands/` -- Configures git and generates GitHub App tokens (JWT auth) -- Queries Jira API for labeled issues (excluding those with `agent-processed`) -- Runs jira-solve for each issue using Claude Code CLI with `--system-prompt` -- Pushes branches to fork, creates PRs to upstream openshift/hypershift -- Implements rate limiting (60s between issues) -- Adds `agent-processed` label to successfully processed issues - -## Configuration - -### Secrets Required - -The workflow requires a single secret in the `test-credentials` namespace: - -**`hypershift-team-claude-prow`** -- Mount path: `/var/run/claude-code-service-account` -- Required keys: - - `claude-prow`: GCP service account JSON key for Vertex AI authentication - - `app-id`: GitHub App ID - - `private-key`: GitHub App private key for JWT signing - - `installation-id`: GitHub App installation ID for hypershift-community fork - - `o-h-installation-id`: GitHub App installation ID for openshift/hypershift upstream - -The workflow uses GitHub App authentication (JWT-based) rather than personal access tokens. This provides better security and allows fine-grained permissions. - -**Optional:** -- `hypershift-jira-token`: Jira API token for adding `agent-processed` labels -- `slack-webhook-url`: Slack incoming webhook URL for posting PR notifications to team-ocp-hypershift -- `gh-to-slack-ids`: JSON mapping of GitHub usernames to Slack member IDs, plus a `backup-user` key for fallback (e.g., `{"gh-username": "UXXXXXXXXXX", "backup-user": "UXXXXXXXXXX"}`) - -These should be configured in Vault with secretsync metadata and synced automatically. - -### Periodic Job - -Configured in `ci-operator/config/openshift/hypershift/openshift-hypershift-main.yaml`: - -```yaml -- as: periodic-jira-agent - cron: 0 9 * * * # Daily at 9:00 AM UTC - steps: - env: - JIRA_AGENT_MAX_ISSUES: "1" # Start with 1 for testing, increase later - workflow: hypershift-jira-agent -``` - -### Environment Variables - -- **`JIRA_AGENT_MAX_ISSUES`** (default: `1`) - - Maximum number of issues to process per run - - Set to `1` initially for safe testing - - Can be increased to `5`, `10`, or higher once validated - - Counts both successful and failed processing attempts - -### State Management - -State is tracked using Jira labels: -- **Label**: `agent-processed` -- When an issue is successfully processed, the `agent-processed` label is added -- The JQL query excludes issues with this label, preventing reprocessing -- Failed issues are NOT labeled, allowing automatic retry on subsequent runs - -To reprocess an issue: -1. Remove the `agent-processed` label from the Jira issue -2. The issue will be picked up on the next run - -## How It Works - -### Non-Interactive Execution - -The workflow uses Claude Code CLI's non-interactive mode with a system prompt: - -```bash -claude -p "$ISSUE_KEY origin --ci" \ - --system-prompt "$SKILL_CONTENT" \ - --allowedTools "Bash Read Write Edit Grep Glob WebFetch" \ - --max-turns 100 \ - --verbose \ - --output-format stream-json -``` - -The jira-solve command is loaded from `ai-helpers/plugins/jira/commands/solve.md` and passed as a system prompt. This allows Claude to analyze the Jira issue and create a PR automatically. - -### Jira Query - -Issues are queried using JQL: -``` -project in (OCPBUGS, CNTRLPLANE) AND resolution = Unresolved AND status in (New, "To Do") AND labels = issue-for-agent AND labels != agent-processed -``` - -Maximum issues queried and processed is controlled by `JIRA_AGENT_MAX_ISSUES` (default: 1). - -### Rate Limiting - -- 60 seconds between processing each issue -- Maximum 100 agentic turns per issue -- Maximum issues per run: configurable via `JIRA_AGENT_MAX_ISSUES` -- Runs once daily at 9:00 AM UTC - -## Container Image - -Uses the `claude-ai-helpers` image from OpenShift CI containing: -- Claude Code CLI -- GitHub CLI (gh) -- jq, git, curl -- Required dependencies - -## Local Testing - -Use the test script: - -```bash -export ANTHROPIC_API_KEY=your-key -export GITHUB_TOKEN=your-token -./tools/hypershift-jira-agent/test-locally.sh -``` - -## Monitoring - -### Success Indicators -- Issues processed successfully with PRs created -- `agent-processed` label added to processed issues -- No authentication errors - -### Failure Indicators -- Failed to authenticate with Claude API -- Failed to create PRs (GitHub auth issues) -- Individual issue processing failures - -### Logs -Check Prow job logs for: -- Jira query results -- Processing output for each issue -- PR URLs created -- Error messages - -## Maintenance - -### Adding/Removing Issues -Add or remove the `issue-for-agent` label in Jira to control which issues are processed. - -### Reprocessing an Issue -To reprocess an issue, remove the `agent-processed` label from the Jira issue: -1. Open the issue in Jira -2. Remove the `agent-processed` label -3. The issue will be picked up on the next scheduled run - -### Adjusting Frequency -Modify the `cron` schedule in the CI config file. Currently runs daily at 9:00 AM UTC. - -### Adjusting Issue Limit -Modify the `JIRA_AGENT_MAX_ISSUES` environment variable in the CI config file: -```yaml -env: - JIRA_AGENT_MAX_ISSUES: "5" # Increase from 1 to 5 -``` -Then run `make update` to regenerate job configs. - -## Troubleshooting - -### Issue: No issues being processed -- Check Jira query returns results -- Verify `issue-for-agent` label exists on issues -- Verify `agent-processed` label is NOT on issues (or remove it to reprocess) - -### Issue: Authentication failures -- Verify secrets are mounted correctly -- Check API keys are valid and not expired -- Ensure GitHub token has required permissions - -### Issue: PR creation fails -- Check GitHub token permissions -- Verify HyperShift repository access -- Review `/jira-solve` command output in logs - -## Future Enhancements - -- Metrics push to Prometheus -- Automatic retries for transient failures -- Priority-based processing -- Issue assignment tracking diff --git a/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.yaml b/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.yaml index 42857ce8af563..cad7046952060 100644 --- a/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.yaml +++ b/ci-operator/step-registry/hypershift/jira-agent/hypershift-jira-agent-workflow.yaml @@ -2,22 +2,35 @@ workflow: as: hypershift-jira-agent steps: pre: - - ref: hypershift-jira-agent-setup + - ref: jira-agent-setup test: - - ref: hypershift-jira-agent-process + - ref: jira-agent-process post: - - ref: hypershift-jira-agent-report + - ref: jira-agent-report + env: + JIRA_AGENT_FORK_REPO: "hypershift-community/hypershift" + JIRA_AGENT_UPSTREAM_REPO: "openshift/hypershift" + JIRA_AGENT_JQL: 'project in (OCPBUGS, CNTRLPLANE) AND resolution = Unresolved AND status in (New, "To Do") AND labels = issue-for-agent AND labels != agent-processed' + JIRA_AGENT_TARGET_STATUS: '{"OCPBUGS":"ASSIGNED","CNTRLPLANE":"Code Review"}' + JIRA_AGENT_ASSIGNEE: "hypershift-automation" + JIRA_AGENT_UPSTREAM_INSTALLATION_ID_KEY: "o-h-installation-id" + JIRA_AGENT_FORK_INSTALLATION_ID_KEY: "installation-id" + JIRA_AGENT_EXTRA_PLUGIN_COMMANDS: | + claude plugin install utils@ai-helpers + claude plugin install golang@ai-helpers + claude plugin marketplace add enxebre/ai-scripts + claude plugin install git@enxebre + JIRA_AGENT_TOOL_SETUP_SCRIPT: "GOFLAGS='' go install golang.org/x/tools/gopls@v0.21.0 && python3.9 -m ensurepip --user 2>/dev/null || true && python3.9 -m pip install --user pre-commit 2>&1 | tail -1" + JIRA_AGENT_REVIEW_LANGUAGE: "go" + JIRA_AGENT_REVIEW_PROFILE: "hypershift" + JIRA_AGENT_SLACK_EMOJI: ":hypershift-bot:" documentation: |- - HyperShift Jira Agent workflow for automated issue processing. + HyperShift-specific wrapper for the generic Jira Agent workflow. - This workflow: - 1. Setup: Verifies Claude Code CLI is available - 2. Process: For each Jira issue, runs a four-phase pipeline: - a. Phase 1 - Solve: Runs /jira-solve to implement, commit, and push changes - b. Phase 2 - Review: Runs /code-review:pre-commit-review to review code quality (read-only) - c. Phase 3 - Fix: Addresses review findings by editing code and pushing fixes - d. Phase 4 - PR: Creates a draft PR after review is complete - 3. Report: Generates HTML report with per-phase token usage, cost estimates, and posts link on PRs + This workflow delegates to the generic jira-agent steps with HyperShift-specific + configuration (fork repo, JQL query, status transitions, plugins, etc.). - The workflow uses /jira-solve and /code-review:pre-commit-review in non-interactive mode. - Issues are queried from Jira with: project in (OCPBUGS, CNTRLPLANE) AND status in (New, "To Do") AND labels = issue-for-agent + Credentials: Uses hypershift-team-claude-prow (configured in generic step refs). + When another team onboards, they will need to either: + 1. Create their own ref YAMLs pointing to the generic commands with their credential + 2. Request the generic credential name be updated to a shared secret diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/OWNERS b/ci-operator/step-registry/hypershift/jira-agent/process/OWNERS deleted file mode 100644 index e39269bf55090..0000000000000 --- a/ci-operator/step-registry/hypershift/jira-agent/process/OWNERS +++ /dev/null @@ -1,12 +0,0 @@ -approvers: -- bryan-cox -- csrwng -- celebdor -- enxebre -- sjenning -reviewers: -- bryan-cox -- csrwng -- celebdor -- enxebre -- sjenning diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml b/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml deleted file mode 100644 index 5cfad84f6418b..0000000000000 --- a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml +++ /dev/null @@ -1,68 +0,0 @@ - -ref: - as: hypershift-jira-agent-process - from: claude-ai-helpers - commands: hypershift-jira-agent-process-commands.sh - timeout: 14400s - env: - - name: CLAUDE_CODE_USE_VERTEX - default: "1" - documentation: |- - Enable Vertex AI for Claude Code. - - name: CLOUD_ML_REGION - default: "global" - documentation: |- - Google Cloud region for Vertex AI. - - name: ANTHROPIC_VERTEX_PROJECT_ID - default: "itpc-gcp-hybrid-pe-eng-claude" - documentation: |- - Google Cloud project ID for Vertex AI authentication. - - name: GOOGLE_APPLICATION_CREDENTIALS - default: "/var/run/claude-code-service-account/claude-prow" - documentation: |- - Path to the Google Cloud service account JSON key file for Vertex AI authentication. - - name: JIRA_AGENT_ISSUE_KEY - default: "" - documentation: |- - Optional override to process a specific Jira issue instead of querying. - When set (e.g., "CNTRLPLANE-2784"), skips the JQL query and processes - only this issue. Leave empty for normal JQL-based discovery. - - name: MULTISTAGE_PARAM_OVERRIDE_JIRA_AGENT_ISSUE_KEY - default: "" - documentation: |- - Gangway API override for JIRA_AGENT_ISSUE_KEY. When triggering - this job via the Gangway API, pass it as: - "pod_spec_options": { - "envs": { - "MULTISTAGE_PARAM_OVERRIDE_JIRA_AGENT_ISSUE_KEY": "CNTRLPLANE-2784" - } - } - - name: JIRA_AGENT_MAX_ISSUES - default: "1" - documentation: |- - Maximum number of Jira issues to process per run. Defaults to 1 for conservative processing. - - name: CLAUDE_MODEL - default: "claude-opus-4-6" - documentation: |- - Claude model to use for processing Jira issues. - resources: - requests: - cpu: 500m - memory: 1Gi - credentials: - - namespace: test-credentials - name: hypershift-team-claude-prow - mount_path: /var/run/claude-code-service-account - documentation: |- - Process step for the HyperShift Jira agent periodic job. - This step runs a four-phase pipeline for each issue: - Phase 1 - Solve: Runs /jira-solve to implement changes, commit, and push the branch (no PR created) - Phase 2 - Review: Runs /code-review:pre-commit-review to review code quality (read-only) - Phase 3 - Fix: Addresses review findings by editing code, committing, and pushing fixes - Phase 4 - PR Creation: Creates a draft PR via gh CLI after review is complete - Token usage (input/output) is extracted per phase and saved for reporting. - Post-processing: Adds 'agent-processed' label, transitions issue status, sets assignee - - Queries Jira for issues with label 'issue-for-agent' (excluding 'agent-processed') - - Failed issues are retried on subsequent runs - - If the review skill is unavailable or fails, the PR is still created - - Uses Vertex AI for Claude authentication via GCP service account diff --git a/ci-operator/step-registry/hypershift/jira-agent/report/OWNERS b/ci-operator/step-registry/hypershift/jira-agent/report/OWNERS deleted file mode 100644 index e39269bf55090..0000000000000 --- a/ci-operator/step-registry/hypershift/jira-agent/report/OWNERS +++ /dev/null @@ -1,12 +0,0 @@ -approvers: -- bryan-cox -- csrwng -- celebdor -- enxebre -- sjenning -reviewers: -- bryan-cox -- csrwng -- celebdor -- enxebre -- sjenning diff --git a/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-ref.yaml b/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-ref.yaml deleted file mode 100644 index 0bf59445bd783..0000000000000 --- a/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-ref.yaml +++ /dev/null @@ -1,12 +0,0 @@ -ref: - as: hypershift-jira-agent-report - from: claude-ai-helpers - commands: hypershift-jira-agent-report-commands.sh - resources: - requests: - cpu: 100m - memory: 256Mi - documentation: |- - Generates an HTML report from the jira-agent processing output. - Parses stream-json output from all three phases (solve, review, PR) - and produces a readable report in ${ARTIFACT_DIR}. diff --git a/ci-operator/step-registry/hypershift/jira-agent/setup/OWNERS b/ci-operator/step-registry/hypershift/jira-agent/setup/OWNERS deleted file mode 100644 index e39269bf55090..0000000000000 --- a/ci-operator/step-registry/hypershift/jira-agent/setup/OWNERS +++ /dev/null @@ -1,12 +0,0 @@ -approvers: -- bryan-cox -- csrwng -- celebdor -- enxebre -- sjenning -reviewers: -- bryan-cox -- csrwng -- celebdor -- enxebre -- sjenning diff --git a/ci-operator/step-registry/jira-agent/OWNERS b/ci-operator/step-registry/jira-agent/OWNERS new file mode 100644 index 0000000000000..ff943340794d2 --- /dev/null +++ b/ci-operator/step-registry/jira-agent/OWNERS @@ -0,0 +1,12 @@ +approvers: + - bryan-cox + - csrwng + - celebdor + - enxebre + - sjenning +reviewers: + - bryan-cox + - csrwng + - celebdor + - enxebre + - sjenning diff --git a/ci-operator/step-registry/jira-agent/README.md b/ci-operator/step-registry/jira-agent/README.md new file mode 100644 index 0000000000000..7e8b90b1a21ea --- /dev/null +++ b/ci-operator/step-registry/jira-agent/README.md @@ -0,0 +1,80 @@ +# jira-agent Step Registry + +Generic, reusable Jira Agent workflow for automated issue processing using Claude Code. + +## Overview + +This step registry provides a parameterized workflow that: +1. **Setup** — Verifies Claude Code CLI is available with Vertex AI authentication +2. **Process** — Runs a four-phase pipeline for each Jira issue: + - Phase 1: Solve (implement changes, commit, push) + - Phase 2: Review (pre-commit code review, read-only) + - Phase 3: Fix (address review findings, commit, push) + - Phase 4: PR Creation (create draft PR via `gh`) +3. **Report** — Generates an HTML report with token usage and cost breakdown + +## Quick Start + +Create a wrapper workflow in your team's directory that references the generic steps and sets your team-specific env vars: + +```yaml +workflow: + as: my-team-jira-agent + steps: + pre: + - ref: jira-agent-setup + test: + - ref: jira-agent-process + post: + - ref: jira-agent-report + env: + JIRA_AGENT_FORK_REPO: "my-org/my-repo" + JIRA_AGENT_UPSTREAM_REPO: "openshift/my-repo" + JIRA_AGENT_JQL: 'project = MYPROJ AND resolution = Unresolved AND labels = issue-for-agent' +``` + +**Credentials:** The generic ref YAMLs use the `hypershift-team-claude-prow` Vault secret. +Teams using a different secret must create their own ref YAMLs (setup + process) that +reference the same command scripts but with their credential name. Workflow-level `env` +cannot override a ref's `credentials` block. + +## Required Environment Variables + +| Variable | Description | +|----------|-------------| +| `JIRA_AGENT_FORK_REPO` | Fork repo in `org/repo` format (e.g., `my-org/my-repo`) | +| `JIRA_AGENT_UPSTREAM_REPO` | Upstream repo in `org/repo` format (e.g., `openshift/my-repo`) | +| `JIRA_AGENT_JQL` | JQL query to find issues to process | + +## Optional Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `JIRA_AGENT_TARGET_STATUS` | `""` | JSON map of project prefix → target status | +| `JIRA_AGENT_ASSIGNEE` | `""` | Display name for assignee lookup | +| `JIRA_AGENT_UPSTREAM_INSTALLATION_ID_KEY` | `o-h-installation-id` | Vault key for upstream GH App installation ID | +| `JIRA_AGENT_FORK_INSTALLATION_ID_KEY` | `installation-id` | Vault key for fork GH App installation ID | +| `JIRA_AGENT_EXTRA_PLUGIN_COMMANDS` | `""` | Newline-separated Claude plugin install commands | +| `JIRA_AGENT_TOOL_SETUP_SCRIPT` | `""` | Inline shell for project-specific tool installs | +| `JIRA_AGENT_REVIEW_LANGUAGE` | `go` | Language for code-review plugin | +| `JIRA_AGENT_REVIEW_PROFILE` | `""` | Profile for code-review plugin | +| `JIRA_AGENT_SLACK_EMOJI` | `:robot:` | Slack emoji for notifications | +| `JIRA_AGENT_MAX_ISSUES` | `1` | Max issues per run | +| `JIRA_AGENT_ISSUE_KEY` | `""` | Override to process a specific issue | +| `CLAUDE_MODEL` | `claude-opus-4-6` | Claude model to use | +| `JIRA_BASE_URL` | `https://redhat.atlassian.net` | Jira instance base URL | + +## Credentials + +Each team needs a Vault secret containing: +- `claude-prow` — GCP service account JSON for Vertex AI +- `jira-pat` — Jira API token (Basic auth) +- `jira-email` — Jira account email +- `app-id` — GitHub App ID +- `private-key` — GitHub App private key (PEM) +- `installation-id` — Fork GitHub App installation ID (key name configurable via `JIRA_AGENT_FORK_INSTALLATION_ID_KEY`) +- Upstream installation ID (key name configurable via `JIRA_AGENT_UPSTREAM_INSTALLATION_ID_KEY`, default: `o-h-installation-id`) +- `slack-webhook-url` — Slack incoming webhook URL +- `gh-to-slack-ids` — JSON mapping of GitHub usernames to Slack user IDs (optional) + +See the onboarding guide in `openshift/hypershift` docs for full setup instructions. diff --git a/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.metadata.json b/ci-operator/step-registry/jira-agent/jira-agent-workflow.metadata.json similarity index 72% rename from ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.metadata.json rename to ci-operator/step-registry/jira-agent/jira-agent-workflow.metadata.json index 59e74a6fdf8fb..9c077f1c492e8 100644 --- a/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.metadata.json +++ b/ci-operator/step-registry/jira-agent/jira-agent-workflow.metadata.json @@ -1,5 +1,5 @@ { - "path": "hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.yaml", + "path": "jira-agent/jira-agent-workflow.yaml", "owners": { "approvers": [ "bryan-cox", diff --git a/ci-operator/step-registry/jira-agent/jira-agent-workflow.yaml b/ci-operator/step-registry/jira-agent/jira-agent-workflow.yaml new file mode 100644 index 0000000000000..2f58105a30105 --- /dev/null +++ b/ci-operator/step-registry/jira-agent/jira-agent-workflow.yaml @@ -0,0 +1,23 @@ +workflow: + as: jira-agent + steps: + pre: + - ref: jira-agent-setup + test: + - ref: jira-agent-process + post: + - ref: jira-agent-report + documentation: |- + Generic Jira Agent workflow for automated issue processing. + + This workflow runs a four-phase pipeline for each matching Jira issue: + 1. Setup: Verifies Claude Code CLI is available + 2. Process: Solve, review, fix, and create PR for each issue + 3. Report: Generates HTML report with token usage and cost breakdown + + Required env vars (set in your team's wrapper workflow): + - JIRA_AGENT_FORK_REPO: Fork repo (org/repo) for pushing branches + - JIRA_AGENT_UPSTREAM_REPO: Upstream repo (org/repo) for creating PRs + - JIRA_AGENT_JQL: JQL query for finding issues to process + + See the onboarding guide in openshift-eng/ai-helpers for full configuration reference. diff --git a/ci-operator/step-registry/jira-agent/process/OWNERS b/ci-operator/step-registry/jira-agent/process/OWNERS new file mode 100644 index 0000000000000..ff943340794d2 --- /dev/null +++ b/ci-operator/step-registry/jira-agent/process/OWNERS @@ -0,0 +1,12 @@ +approvers: + - bryan-cox + - csrwng + - celebdor + - enxebre + - sjenning +reviewers: + - bryan-cox + - csrwng + - celebdor + - enxebre + - sjenning diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh b/ci-operator/step-registry/jira-agent/process/jira-agent-process-commands.sh old mode 100755 new mode 100644 similarity index 69% rename from ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh rename to ci-operator/step-registry/jira-agent/process/jira-agent-process-commands.sh index fee7b43f3bec0..330dbb18d581a --- a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-commands.sh +++ b/ci-operator/step-registry/jira-agent/process/jira-agent-process-commands.sh @@ -1,7 +1,7 @@ #!/bin/bash set -euo pipefail -echo "=== HyperShift Jira Agent Process ===" +echo "=== Jira Agent Process ===" # Apply Gangway API overrides (MULTISTAGE_PARAM_OVERRIDE_* prefix) if [[ -n "${MULTISTAGE_PARAM_OVERRIDE_JIRA_AGENT_ISSUE_KEY:-}" ]]; then @@ -9,21 +9,48 @@ if [[ -n "${MULTISTAGE_PARAM_OVERRIDE_JIRA_AGENT_ISSUE_KEY:-}" ]]; then export JIRA_AGENT_ISSUE_KEY="${MULTISTAGE_PARAM_OVERRIDE_JIRA_AGENT_ISSUE_KEY}" fi +# Validate required env vars +for required_var in JIRA_AGENT_FORK_REPO JIRA_AGENT_UPSTREAM_REPO; do + if [ -z "${!required_var:-}" ]; then + echo "ERROR: Required env var $required_var is not set" + exit 1 + fi +done +if [ -z "${JIRA_AGENT_ISSUE_KEY:-}" ] && [ -z "${JIRA_AGENT_JQL:-}" ]; then + echo "ERROR: JIRA_AGENT_JQL must be set when JIRA_AGENT_ISSUE_KEY is not provided" + exit 1 +fi + +# Derive org name from fork repo slug +FORK_ORG="${JIRA_AGENT_FORK_REPO%%/*}" + +# Configurable defaults +UPSTREAM_INSTALL_ID_KEY="${JIRA_AGENT_UPSTREAM_INSTALLATION_ID_KEY:-o-h-installation-id}" +FORK_INSTALL_ID_KEY="${JIRA_AGENT_FORK_INSTALLATION_ID_KEY:-installation-id}" +REVIEW_LANGUAGE="${JIRA_AGENT_REVIEW_LANGUAGE:-go}" +REVIEW_PROFILE="${JIRA_AGENT_REVIEW_PROFILE:-}" +SLACK_EMOJI="${JIRA_AGENT_SLACK_EMOJI:-:robot:}" +JIRA_BASE_URL="${JIRA_BASE_URL:-https://redhat.atlassian.net}" + # State file for sharing results with report step STATE_FILE="${SHARED_DIR}/processed-issues.txt" -# Clone ai-helpers repository (contains /jira-solve command) +# Clone ai-helpers repository (contains /jira-solve command and shared libraries) echo "Cloning ai-helpers repository..." git clone https://github.com/openshift-eng/ai-helpers /tmp/ai-helpers -# Clone HyperShift fork (we push here and create PRs to upstream) -echo "Cloning HyperShift repository..." -git clone https://github.com/hypershift-community/hypershift /tmp/hypershift +# Source reusable CI skills +source /tmp/ai-helpers/plugins/ci/skills/github-app-auth/github-app-auth.sh +source /tmp/ai-helpers/plugins/ci/skills/slack-pr-notify/slack-pr-notify.sh + +# Clone the project fork (we push here and create PRs to upstream) +echo "Cloning ${JIRA_AGENT_FORK_REPO}..." +git clone "https://github.com/${JIRA_AGENT_FORK_REPO}" /tmp/project-repo -# Copy jira-solve command from ai-helpers to hypershift +# Copy jira-solve command from ai-helpers to project repo echo "Setting up Claude commands..." -mkdir -p /tmp/hypershift/.claude/commands -cp /tmp/ai-helpers/plugins/jira/commands/solve.md /tmp/hypershift/.claude/commands/jira-solve.md +mkdir -p /tmp/project-repo/.claude/commands +cp /tmp/ai-helpers/plugins/jira/commands/solve.md /tmp/project-repo/.claude/commands/jira-solve.md # Check if code-review plugin is available for Phase 2 REVIEW_PLUGIN_DIR="/tmp/ai-helpers/plugins/code-review" @@ -33,30 +60,40 @@ if [ ! -d "${REVIEW_PLUGIN_DIR}/.claude-plugin" ]; then fi echo "Code-review plugin found" -# Install tool dependencies -echo "Installing tool dependencies..." -GOFLAGS="" go install golang.org/x/tools/gopls@v0.21.0 -python3.9 -m ensurepip --user 2>/dev/null || true -python3.9 -m pip install --user pre-commit 2>&1 | tail -1 +# Install tool dependencies (project-specific) +# Trust boundary: these env vars come from the workflow YAML authored by team members +if [ -n "${JIRA_AGENT_TOOL_SETUP_SCRIPT:-}" ]; then + echo "Running project-specific tool setup..." + eval "$JIRA_AGENT_TOOL_SETUP_SCRIPT" +fi export PATH="${GOPATH:-$HOME/go}/bin:$HOME/.local/bin:$PATH" +# Force HTTPS for plugin installs (claude CLI defaults to SSH which lacks host keys in CI) +git config --global url."https://github.com/".insteadOf "git@github.com:" + # Install plugins echo "Installing Claude Code plugins..." claude plugin marketplace add openshift-eng/ai-helpers -claude plugin install utils@ai-helpers -claude plugin install golang@ai-helpers -claude plugin marketplace add enxebre/ai-scripts -claude plugin install git@enxebre -cd /tmp/hypershift +# Run any extra plugin setup commands +if [ -n "${JIRA_AGENT_EXTRA_PLUGIN_COMMANDS:-}" ]; then + echo "Running extra plugin commands..." + while IFS= read -r cmd; do + if [ -n "$cmd" ]; then + eval "$cmd" + fi + done <<< "$JIRA_AGENT_EXTRA_PLUGIN_COMMANDS" +fi + +cd /tmp/project-repo # Configure git git config user.name "OpenShift CI Bot" git config user.email "ci-bot@redhat.com" # Sync fork with upstream before doing any work -echo "Syncing fork with upstream openshift/hypershift..." -git remote add upstream https://github.com/openshift/hypershift.git +echo "Syncing fork with upstream ${JIRA_AGENT_UPSTREAM_REPO}..." +git remote add upstream "https://github.com/${JIRA_AGENT_UPSTREAM_REPO}.git" git fetch upstream main git checkout main git rebase upstream/main @@ -67,11 +104,11 @@ echo "Generating GitHub App token..." GITHUB_APP_CREDS_DIR="/var/run/claude-code-service-account" APP_ID_FILE="${GITHUB_APP_CREDS_DIR}/app-id" -INSTALLATION_ID_FILE="${GITHUB_APP_CREDS_DIR}/installation-id" +INSTALLATION_ID_FILE="${GITHUB_APP_CREDS_DIR}/${FORK_INSTALL_ID_KEY}" PRIVATE_KEY_FILE="${GITHUB_APP_CREDS_DIR}/private-key" # Check if all required credentials exist -INSTALLATION_ID_UPSTREAM_FILE="${GITHUB_APP_CREDS_DIR}/o-h-installation-id" +INSTALLATION_ID_UPSTREAM_FILE="${GITHUB_APP_CREDS_DIR}/${UPSTREAM_INSTALL_ID_KEY}" if [ ! -f "$APP_ID_FILE" ] || [ ! -f "$INSTALLATION_ID_FILE" ] || [ ! -f "$PRIVATE_KEY_FILE" ] || [ ! -f "$INSTALLATION_ID_UPSTREAM_FILE" ]; then echo "GitHub App credentials not yet available in ${GITHUB_APP_CREDS_DIR}" @@ -80,67 +117,53 @@ if [ ! -f "$APP_ID_FILE" ] || [ ! -f "$INSTALLATION_ID_FILE" ] || [ ! -f "$PRIVA echo "" echo "Waiting for Vault secretsync to complete. The following keys are required:" echo " - app-id" - echo " - installation-id (for hypershift-community fork)" - echo " - o-h-installation-id (for openshift/hypershift upstream)" + echo " - ${FORK_INSTALL_ID_KEY} (for ${FORK_ORG} fork)" + echo " - ${UPSTREAM_INSTALL_ID_KEY} (for ${JIRA_AGENT_UPSTREAM_REPO} upstream)" echo " - private-key" echo "" - echo "Exiting gracefully. Re-run once secrets are synced." - exit 0 + echo "ERROR: Required credentials are missing. Re-run once secrets are synced." + echo "no_credentials" > "${SHARED_DIR}/processed-issues.txt" + exit 1 fi -APP_ID=$(cat "$APP_ID_FILE") +# Disable tracing for credential handling +[[ $- == *x* ]] && _TOKEN_WAS_TRACING=true || _TOKEN_WAS_TRACING=false +set +x + INSTALLATION_ID_FORK=$(cat "$INSTALLATION_ID_FILE") INSTALLATION_ID_UPSTREAM=$(cat "$INSTALLATION_ID_UPSTREAM_FILE") -# Function to generate GitHub App token for a given installation ID -generate_github_token() { - local INSTALL_ID=$1 - local NOW - NOW=$(date +%s) - local IAT=$((NOW - 60)) - local EXP=$((NOW + 600)) - - local HEADER - HEADER=$(echo -n '{"alg":"RS256","typ":"JWT"}' | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n') - local PAYLOAD - PAYLOAD=$(echo -n "{\"iat\":${IAT},\"exp\":${EXP},\"iss\":\"${APP_ID}\"}" | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n') - local SIGNATURE - SIGNATURE=$(echo -n "${HEADER}.${PAYLOAD}" | openssl dgst -sha256 -sign "$PRIVATE_KEY_FILE" | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n') - local JWT="${HEADER}.${PAYLOAD}.${SIGNATURE}" - - curl -s -X POST \ - -H "Authorization: Bearer ${JWT}" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/app/installations/${INSTALL_ID}/access_tokens" \ - | jq -r '.token' -} +# generate_github_token() is provided by /tmp/ai-helpers/plugins/ci/lib/github-app-auth.sh -# Generate token for fork (hypershift-community/hypershift) - for pushing branches +# Generate token for fork - for pushing branches echo "Generating GitHub App token for fork..." GITHUB_TOKEN_FORK=$(generate_github_token "$INSTALLATION_ID_FORK") if [ -z "$GITHUB_TOKEN_FORK" ] || [ "$GITHUB_TOKEN_FORK" = "null" ]; then echo "ERROR: Failed to generate GitHub App token for fork" + $_TOKEN_WAS_TRACING && set -x exit 1 fi echo "Fork token generated successfully" -# Generate token for upstream (openshift/hypershift) - for creating PRs +# Generate token for upstream - for creating PRs echo "Generating GitHub App token for upstream..." GITHUB_TOKEN_UPSTREAM=$(generate_github_token "$INSTALLATION_ID_UPSTREAM") if [ -z "$GITHUB_TOKEN_UPSTREAM" ] || [ "$GITHUB_TOKEN_UPSTREAM" = "null" ]; then echo "ERROR: Failed to generate GitHub App token for upstream" + $_TOKEN_WAS_TRACING && set -x exit 1 fi echo "Upstream token generated successfully" # Configure git to use the fork token for push operations via credential helper -# Using credential helper instead of URL rewriting prevents token leaking in git remote output git config --global credential.helper "!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN_FORK}; }; f" # Export upstream token as GITHUB_TOKEN for gh CLI (used for PR creation) export GITHUB_TOKEN="$GITHUB_TOKEN_UPSTREAM" echo "GitHub App tokens configured successfully" +$_TOKEN_WAS_TRACING && set -x + # Configuration: maximum issues to process per run (default: 1) MAX_ISSUES=${JIRA_AGENT_MAX_ISSUES:-1} echo "Configuration: MAX_ISSUES=$MAX_ISSUES" @@ -151,6 +174,8 @@ SUBAGENT_PROMPT="SUBAGENTS: Launch ALL subagents in parallel (single message wit # Load Jira API credentials for Atlassian Cloud (Basic Auth: email:api-token) JIRA_TOKEN_FILE="/var/run/claude-code-service-account/jira-pat" JIRA_EMAIL_FILE="/var/run/claude-code-service-account/jira-email" +[[ $- == *x* ]] && _JIRA_WAS_TRACING=true || _JIRA_WAS_TRACING=false +set +x if [ -f "$JIRA_TOKEN_FILE" ] && [ -f "$JIRA_EMAIL_FILE" ]; then JIRA_TOKEN=$(cat "$JIRA_TOKEN_FILE") JIRA_EMAIL=$(cat "$JIRA_EMAIL_FILE") @@ -162,6 +187,7 @@ else JIRA_TOKEN="" JIRA_AUTH="" fi +$_JIRA_WAS_TRACING && set -x # Load Slack webhook URL for notifications (tracing disabled to protect credential) SLACK_WEBHOOK_FILE="/var/run/claude-code-service-account/slack-webhook-url" @@ -193,14 +219,6 @@ else GITHUB_SLACK_MAP="{}" fi -# Extract Slack fallback user ID from mapping (pinged when no reviewers are assigned) -SLACK_FALLBACK_USER_ID=$(jq -r '.["backup-user"] // empty' <<<"$GITHUB_SLACK_MAP") -if [ -n "$SLACK_FALLBACK_USER_ID" ]; then - echo "Slack fallback user ID loaded from mapping" -else - echo "Warning: No 'backup-user' key in GitHub-to-Slack mapping" -fi - # Function to transition a Jira issue to a target status transition_issue() { local ISSUE_KEY=$1 @@ -208,7 +226,7 @@ transition_issue() { # Get available transitions TRANSITIONS=$(curl -s \ - "https://redhat.atlassian.net/rest/api/3/issue/$ISSUE_KEY/transitions" \ + "${JIRA_BASE_URL}/rest/api/3/issue/$ISSUE_KEY/transitions" \ -H "Authorization: Basic $JIRA_AUTH" \ -H "Content-Type: application/json") @@ -217,12 +235,16 @@ transition_issue() { '.transitions[] | select(.name == $status) | .id' | head -1) if [ -n "$TRANSITION_ID" ] && [ "$TRANSITION_ID" != "null" ]; then - curl -s -X POST \ - "https://redhat.atlassian.net/rest/api/3/issue/$ISSUE_KEY/transitions" \ + TRANSITION_HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + "${JIRA_BASE_URL}/rest/api/3/issue/$ISSUE_KEY/transitions" \ -H "Authorization: Basic $JIRA_AUTH" \ -H "Content-Type: application/json" \ - -d "{\"transition\":{\"id\":\"$TRANSITION_ID\"}}" - return 0 + -d "{\"transition\":{\"id\":\"$TRANSITION_ID\"}}") + if [ "$TRANSITION_HTTP_CODE" = "204" ] || [ "$TRANSITION_HTTP_CODE" = "200" ]; then + return 0 + fi + echo " Warning: Jira transition API returned HTTP $TRANSITION_HTTP_CODE" + return 1 else echo " Warning: Transition to '$TARGET_STATUS' not available" return 1 @@ -235,104 +257,13 @@ set_assignee() { local ACCOUNT_ID=$2 curl -s -w "\n%{http_code}" -X PUT \ - "https://redhat.atlassian.net/rest/api/3/issue/$ISSUE_KEY/assignee" \ + "${JIRA_BASE_URL}/rest/api/3/issue/$ISSUE_KEY/assignee" \ -H "Authorization: Basic $JIRA_AUTH" \ -H "Content-Type: application/json" \ -d "{\"accountId\":\"$ACCOUNT_ID\"}" } -# Function to send Slack notification after PR creation -send_slack_notification() { - local PR_URL=$1 - local PR_NUM=$2 - - if [ -z "$SLACK_WEBHOOK_URL" ]; then - echo " Skipping Slack notification (no webhook URL configured)" - return 0 - fi - - echo " Polling for PR reviewers (up to 2 minutes)..." - local REVIEWERS="" - local PR_TITLE="" - local ATTEMPT=0 - local MAX_ATTEMPTS=5 - - while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do - local PR_DATA - PR_DATA=$(gh pr view "$PR_NUM" --repo openshift/hypershift --json reviewRequests,title 2>/dev/null || echo "{}") - PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // empty' 2>/dev/null) - REVIEWERS=$(echo "$PR_DATA" | jq -r '.reviewRequests[]?.login // empty' 2>/dev/null) - if [ -n "$REVIEWERS" ]; then - echo " Reviewers found: $REVIEWERS" - break - fi - ATTEMPT=$((ATTEMPT + 1)) - if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then - echo " No reviewers yet, retrying in 30s (attempt $ATTEMPT/$MAX_ATTEMPTS)..." - sleep 30 - fi - done - - # Fallback PR title if not fetched - if [ -z "$PR_TITLE" ]; then - PR_TITLE="PR #${PR_NUM}" - fi - - # Build reviewer mention string - local REVIEWER_MENTIONS="" - if [ -n "$REVIEWERS" ]; then - while IFS= read -r gh_user; do - local slack_id - slack_id=$(echo "$GITHUB_SLACK_MAP" | jq -r --arg user "$gh_user" '.[$user] // empty' 2>/dev/null) - if [ -n "$slack_id" ]; then - REVIEWER_MENTIONS="${REVIEWER_MENTIONS} <@${slack_id}>" - else - REVIEWER_MENTIONS="${REVIEWER_MENTIONS} ${gh_user}" - fi - done <<< "$REVIEWERS" - else - echo " No reviewers assigned after 2 minutes, using fallback" - if [ -n "$SLACK_FALLBACK_USER_ID" ]; then - REVIEWER_MENTIONS="<@${SLACK_FALLBACK_USER_ID}>" - else - REVIEWER_MENTIONS="(none assigned)" - fi - fi - REVIEWER_MENTIONS=$(echo "$REVIEWER_MENTIONS" | sed 's/^ //') - - # Send Slack message (tracing disabled to protect webhook URL) - local SLACK_PAYLOAD - SLACK_PAYLOAD=$(jq -n --arg title "$PR_TITLE" --arg url "$PR_URL" --arg reviewers "$REVIEWER_MENTIONS" \ - '{text: ":hypershift-bot: *Jira Agent PR ready for review*\n:review: <\($url)|\($title)>\n:eyes: Reviewers: \($reviewers)"}') - - [[ $- == *x* ]] && local _was_tracing=true || local _was_tracing=false - set +x - set +e - local SLACK_RESPONSE - SLACK_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ - --connect-timeout 10 \ - --max-time 20 \ - -H 'Content-type: application/json' \ - --data "$SLACK_PAYLOAD" \ - "$SLACK_WEBHOOK_URL") - local CURL_EXIT_CODE=$? - set -e - $_was_tracing && set -x - - if [ $CURL_EXIT_CODE -ne 0 ]; then - echo " Warning: Failed to send Slack notification (curl exit $CURL_EXIT_CODE)" - return 0 - fi - - local SLACK_HTTP_CODE - SLACK_HTTP_CODE=$(echo "$SLACK_RESPONSE" | tail -1) - - if [ "$SLACK_HTTP_CODE" = "200" ]; then - echo " Slack notification sent successfully" - else - echo " Warning: Failed to send Slack notification (HTTP $SLACK_HTTP_CODE)" - fi -} +# send_slack_notification() is provided by /tmp/ai-helpers/plugins/ci/lib/slack-notify.sh # Query Jira for issues (excluding already processed ones via label) echo "Querying Jira for issues..." @@ -340,11 +271,11 @@ if [ -n "${JIRA_AGENT_ISSUE_KEY:-}" ]; then echo "Using override: JIRA_AGENT_ISSUE_KEY=$JIRA_AGENT_ISSUE_KEY" JQL="key = ${JIRA_AGENT_ISSUE_KEY}" else - JQL='project in (OCPBUGS, CNTRLPLANE) AND resolution = Unresolved AND status in (New, "To Do") AND labels = issue-for-agent AND labels != agent-processed' + JQL="$JIRA_AGENT_JQL" fi SEARCH_PAYLOAD=$(jq -n --arg jql "$JQL" --argjson max "$MAX_ISSUES" \ '{jql: $jql, fields: ["key", "summary"], maxResults: $max}') -SEARCH_RESPONSE=$(curl -s -w "\n%{http_code}" "https://redhat.atlassian.net/rest/api/3/search/jql" \ +SEARCH_RESPONSE=$(curl -s -w "\n%{http_code}" "${JIRA_BASE_URL}/rest/api/3/search/jql" \ -X POST \ -H "Authorization: Basic $JIRA_AUTH" \ -H "Content-Type: application/json" \ @@ -396,7 +327,6 @@ while IFS= read -r line; do echo "==========================================" # Run jira-solve command non-interactively using --system-prompt - # (Claude's -p mode doesn't support slash commands directly) TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) echo "Running: jira-solve $ISSUE_KEY origin --ci" @@ -404,11 +334,10 @@ while IFS= read -r line; do PHASE1_START=$(date +%s) # Load the skill content as system prompt - SKILL_CONTENT=$(cat /tmp/hypershift/.claude/commands/jira-solve.md) + SKILL_CONTENT=$(cat /tmp/project-repo/.claude/commands/jira-solve.md) # Additional context for fork-based workflow - # Git push uses fork token (configured via credential helper), gh CLI uses upstream token (GITHUB_TOKEN env var) - FORK_CONTEXT="IMPORTANT: You are working in a fork (hypershift-community/hypershift). Git push is pre-configured to work with the fork. After creating commits on your feature branch, push the branch to origin. Do NOT create a Pull Request - the PR will be created in a subsequent automated step after code review. SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v' or 'git remote get-url origin'. ${SUBAGENT_PROMPT}" + FORK_CONTEXT="IMPORTANT: You are working in a fork (${JIRA_AGENT_FORK_REPO}). Git push is pre-configured to work with the fork. After creating commits on your feature branch, push the branch to origin. Do NOT create a Pull Request - the PR will be created in a subsequent automated step after code review. SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v', 'git remote get-url origin', 'git config --list', 'git config --global credential.helper', or 'cat ~/.gitconfig'. ${SUBAGENT_PROMPT}" set +e # Don't exit on error for individual issues echo "Starting Claude processing with streaming output..." @@ -450,7 +379,7 @@ while IFS= read -r line; do echo "$PHASE1_DURATION" > "${SHARED_DIR}/claude-${ISSUE_KEY}-solve-duration.txt" if [ $EXIT_CODE -eq 0 ]; then - echo "✅ Phase 1 (jira-solve) completed for $ISSUE_KEY" + echo "Phase 1 (jira-solve) completed for $ISSUE_KEY" # Check if code changes were made (branch changed from main) BRANCH_NAME=$(git branch --show-current) @@ -475,12 +404,15 @@ while IFS= read -r line; do PHASE2_START=$(date +%s) - REVIEW_PROMPT="/code-review:pre-commit-review --language go --profile hypershift" + REVIEW_PROMPT="/code-review:pre-commit-review --language ${REVIEW_LANGUAGE}" + if [ -n "$REVIEW_PROFILE" ]; then + REVIEW_PROMPT="${REVIEW_PROMPT} --profile ${REVIEW_PROFILE}" + fi set +e claude -p "$REVIEW_PROMPT" \ --plugin-dir "${REVIEW_PLUGIN_DIR}" \ - --append-system-prompt "SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v' or 'git remote get-url origin'. ${SUBAGENT_PROMPT}" \ + --append-system-prompt "SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v', 'git remote get-url origin', 'git config --list', 'git config --global credential.helper', or 'cat ~/.gitconfig'. ${SUBAGENT_PROMPT}" \ --allowedTools "Bash Read Grep Glob Task" \ --max-turns 225 \ --effort max \ @@ -518,9 +450,9 @@ while IFS= read -r line; do echo "$PHASE2_DURATION" > "${SHARED_DIR}/claude-${ISSUE_KEY}-review-duration.txt" if [ $REVIEW_EXIT_CODE -eq 0 ]; then - echo "✅ Phase 2 (pre-commit review) completed for $ISSUE_KEY" + echo "Phase 2 (pre-commit review) completed for $ISSUE_KEY" else - echo "⚠️ Phase 2 (pre-commit review) failed for $ISSUE_KEY (exit code: $REVIEW_EXIT_CODE)" + echo "Phase 2 (pre-commit review) failed for $ISSUE_KEY (exit code: $REVIEW_EXIT_CODE)" echo "Continuing with PR creation despite review failure..." fi @@ -538,15 +470,18 @@ while IFS= read -r line; do fi # Refresh tokens before Phase 3 since it pushes code. - # Phases 1-2 can exceed the 1-hour GitHub App token lifetime. echo "Refreshing GitHub App tokens before Phase 3..." - GITHUB_TOKEN_FORK=$(generate_github_token "$INSTALLATION_ID_FORK") - if [ -z "$GITHUB_TOKEN_FORK" ] || [ "$GITHUB_TOKEN_FORK" = "null" ]; then - echo "ERROR: Failed to refresh GitHub App token for fork" - else + [[ $- == *x* ]] && _REFRESH3_TRACING=true || _REFRESH3_TRACING=false + set +x + if _NEW_TOKEN=$(generate_github_token "$INSTALLATION_ID_FORK") \ + && [ -n "$_NEW_TOKEN" ] && [ "$_NEW_TOKEN" != "null" ]; then + GITHUB_TOKEN_FORK="$_NEW_TOKEN" git config --global credential.helper "!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN_FORK}; }; f" echo "Fork token refreshed" + else + echo "ERROR: Failed to refresh GitHub App token for fork — continuing with previous token" fi + $_REFRESH3_TRACING && set -x PHASE3_START=$(date +%s) @@ -561,7 +496,7 @@ IMPORTANT: - Run 'make test' and 'make verify' after fixes to verify nothing is broken. - If 'make verify' generates new files, commit those too and run 'make verify' again to confirm it passes. - Commit all fixes and push to origin. -- SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v' or 'git remote get-url origin'. +- SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v', 'git remote get-url origin', 'git config --list', 'git config --global credential.helper', or 'cat ~/.gitconfig'. - ${SUBAGENT_PROMPT}" set +e @@ -599,9 +534,9 @@ IMPORTANT: echo "Phase 3 tokens: $(cat "${SHARED_DIR}/claude-${ISSUE_KEY}-fix-tokens.json")" if [ $FIX_EXIT_CODE -eq 0 ]; then - echo "✅ Phase 3 (address review) completed for $ISSUE_KEY" + echo "Phase 3 (address review) completed for $ISSUE_KEY" else - echo "⚠️ Phase 3 (address review) failed (exit code: $FIX_EXIT_CODE)" + echo "Phase 3 (address review) failed (exit code: $FIX_EXIT_CODE)" echo "Continuing with PR creation..." fi else @@ -614,24 +549,27 @@ IMPORTANT: echo "$PHASE3_DURATION" > "${SHARED_DIR}/claude-${ISSUE_KEY}-fix-duration.txt" # Regenerate GitHub App tokens before Phase 4. - # Phase 3 may also have taken significant time, so refresh again - # to ensure PR creation uses a valid token. echo "Refreshing GitHub App tokens before Phase 4..." - GITHUB_TOKEN_FORK=$(generate_github_token "$INSTALLATION_ID_FORK") - if [ -z "$GITHUB_TOKEN_FORK" ] || [ "$GITHUB_TOKEN_FORK" = "null" ]; then - echo "ERROR: Failed to refresh GitHub App token for fork" - else + [[ $- == *x* ]] && _REFRESH4_TRACING=true || _REFRESH4_TRACING=false + set +x + if _NEW_TOKEN=$(generate_github_token "$INSTALLATION_ID_FORK") \ + && [ -n "$_NEW_TOKEN" ] && [ "$_NEW_TOKEN" != "null" ]; then + GITHUB_TOKEN_FORK="$_NEW_TOKEN" git config --global credential.helper "!f() { echo username=x-access-token; echo password=${GITHUB_TOKEN_FORK}; }; f" echo "Fork token refreshed" + else + echo "ERROR: Failed to refresh GitHub App token for fork — continuing with previous token" fi - GITHUB_TOKEN_UPSTREAM=$(generate_github_token "$INSTALLATION_ID_UPSTREAM") - if [ -z "$GITHUB_TOKEN_UPSTREAM" ] || [ "$GITHUB_TOKEN_UPSTREAM" = "null" ]; then - echo "ERROR: Failed to refresh GitHub App token for upstream" - else + if _NEW_TOKEN=$(generate_github_token "$INSTALLATION_ID_UPSTREAM") \ + && [ -n "$_NEW_TOKEN" ] && [ "$_NEW_TOKEN" != "null" ]; then + GITHUB_TOKEN_UPSTREAM="$_NEW_TOKEN" export GITHUB_TOKEN="$GITHUB_TOKEN_UPSTREAM" echo "Upstream token refreshed" + else + echo "ERROR: Failed to refresh GitHub App token for upstream — continuing with previous token" fi + $_REFRESH4_TRACING && set -x # === Phase 4: Create Pull Request === echo "" @@ -644,15 +582,15 @@ IMPORTANT: PR_PROMPT="Create a pull request for the changes on branch '${BRANCH_NAME}'. Details: - Jira issue: ${ISSUE_KEY} - Jira summary: ${ISSUE_SUMMARY} -- Jira URL: https://redhat.atlassian.net/browse/${ISSUE_KEY} +- Jira URL: ${JIRA_BASE_URL}/browse/${ISSUE_KEY} - Read the PR template at .github/PULL_REQUEST_TEMPLATE.md and use it to structure the PR body. - Use 'git log main..HEAD' to understand what changed and write a meaningful description. - PR title must start with '${ISSUE_KEY}: '. - The PR body MUST end with the following two lines: Always review AI generated responses prior to use. Generated with [Claude Code](https://claude.com/claude-code) via \`/jira:solve ${ISSUE_KEY}\` -- Create the PR by running: gh pr create --repo openshift/hypershift --head hypershift-community:${BRANCH_NAME} --no-maintainer-edit --title '' --body '<body>' -- SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v' or 'git remote get-url origin'. +- Create the PR by running: gh pr create --repo ${JIRA_AGENT_UPSTREAM_REPO} --head ${FORK_ORG}:${BRANCH_NAME} --no-maintainer-edit --title '<title>' --body '<body>' +- SECURITY: Do NOT run commands that reveal git credentials like 'git remote -v', 'git remote get-url origin', 'git config --list', 'git config --global credential.helper', or 'cat ~/.gitconfig'. - ${SUBAGENT_PROMPT}" set +e @@ -693,16 +631,19 @@ IMPORTANT: echo "Phase 4 duration: ${PHASE4_DURATION}s" echo "$PHASE4_DURATION" > "${SHARED_DIR}/claude-${ISSUE_KEY}-pr-duration.txt" + ISSUE_SUCCESS=true if [ $PR_EXIT_CODE -eq 0 ]; then - PR_URL=$(grep -o 'https://github.com/openshift/hypershift/pull/[0-9]*' "/tmp/claude-${ISSUE_KEY}-pr.json" | head -1 || echo "") + PR_URL=$(grep -o "https://github.com/${JIRA_AGENT_UPSTREAM_REPO}/pull/[0-9]*" "/tmp/claude-${ISSUE_KEY}-pr.json" | head -1 || echo "") if [ -n "$PR_URL" ]; then - echo "✅ PR created: $PR_URL" + echo "PR created: $PR_URL" else - echo "⚠️ Phase 4 completed but no PR URL found in output" + echo "Phase 4 completed but no PR URL found in output" + ISSUE_SUCCESS=false fi else - echo "❌ Phase 4 (PR creation) failed for $ISSUE_KEY (exit code: $PR_EXIT_CODE)" + echo "Phase 4 (PR creation) failed for $ISSUE_KEY (exit code: $PR_EXIT_CODE)" PR_URL="" + ISSUE_SUCCESS=false fi # Append report link to PR description @@ -712,22 +653,22 @@ IMPORTANT: REPORT_URL="" if [ -n "${BUILD_ID:-}" ] && [ -n "${JOB_NAME:-}" ]; then if [ "${JOB_TYPE:-}" = "periodic" ]; then - REPORT_URL="https://gcsweb-ci.apps.ci.l2s4.p1.openshiftapps.com/gcs/test-platform-results/logs/${JOB_NAME}/${BUILD_ID}/artifacts/periodic-jira-agent/hypershift-jira-agent-report/artifacts/jira-agent-report.html" + REPORT_URL="https://gcsweb-ci.apps.ci.l2s4.p1.openshiftapps.com/gcs/test-platform-results/logs/${JOB_NAME}/${BUILD_ID}/artifacts/periodic-jira-agent/jira-agent-report/artifacts/jira-agent-report.html" else - REPORT_URL="https://gcsweb-ci.apps.ci.l2s4.p1.openshiftapps.com/gcs/test-platform-results/pr-logs/pull/openshift_release/${PULL_NUMBER:-0}/${JOB_NAME}/${BUILD_ID}/artifacts/periodic-jira-agent/hypershift-jira-agent-report/artifacts/jira-agent-report.html" + REPORT_URL="https://gcsweb-ci.apps.ci.l2s4.p1.openshiftapps.com/gcs/test-platform-results/pr-logs/pull/openshift_release/${PULL_NUMBER:-0}/${JOB_NAME}/${BUILD_ID}/artifacts/periodic-jira-agent/jira-agent-report/artifacts/jira-agent-report.html" fi fi if [ -n "$REPORT_URL" ]; then echo "Appending report link to PR #${PR_NUM} description..." - CURRENT_BODY=$(gh pr view "$PR_NUM" --repo openshift/hypershift --json body -q .body 2>/dev/null || echo "") + CURRENT_BODY=$(gh pr view "$PR_NUM" --repo "${JIRA_AGENT_UPSTREAM_REPO}" --json body -q .body 2>/dev/null || echo "") REPORT_SECTION="--- -> **Note:** This PR was auto-generated by the [jira-agent](https://github.com/openshift/release/tree/main/ci-operator/step-registry/hypershift/jira-agent) periodic CI job in response to [${ISSUE_KEY}](https://redhat.atlassian.net/browse/${ISSUE_KEY}). See the [full report](${REPORT_URL}) for token usage, cost breakdown, and detailed phase output." +> **Note:** This PR was auto-generated by the jira-agent periodic CI job in response to [${ISSUE_KEY}](${JIRA_BASE_URL}/browse/${ISSUE_KEY}). See the [full report](${REPORT_URL}) for token usage, cost breakdown, and detailed phase output." UPDATED_BODY="${CURRENT_BODY} ${REPORT_SECTION}" - gh pr edit "$PR_NUM" --repo openshift/hypershift --body "$UPDATED_BODY" 2>/dev/null || echo "Warning: Failed to update PR #${PR_NUM} description" + gh pr edit "$PR_NUM" --repo "${JIRA_AGENT_UPSTREAM_REPO}" --body "$UPDATED_BODY" 2>/dev/null || echo "Warning: Failed to update PR #${PR_NUM} description" fi fi fi @@ -740,11 +681,11 @@ ${REPORT_SECTION}" echo "No code changes detected for $ISSUE_KEY, skipping review and PR creation" fi - # Add 'agent-processed' label to mark issue as handled - if [ -n "$JIRA_AUTH" ]; then + # Add 'agent-processed' label only when end-to-end processing succeeded + if [ "${ISSUE_SUCCESS:-true}" = true ] && [ -n "$JIRA_AUTH" ]; then echo "Adding 'agent-processed' label to $ISSUE_KEY..." LABEL_RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \ - "https://redhat.atlassian.net/rest/api/3/issue/$ISSUE_KEY" \ + "${JIRA_BASE_URL}/rest/api/3/issue/$ISSUE_KEY" \ -H "Authorization: Basic $JIRA_AUTH" \ -H "Content-Type: application/json" \ -d '{"update":{"labels":[{"add":"agent-processed"}]}}') @@ -755,48 +696,55 @@ ${REPORT_SECTION}" echo " Warning: Failed to add label (HTTP $HTTP_CODE)" fi - # Transition issue to appropriate status based on project - if [[ "$ISSUE_KEY" == OCPBUGS-* ]]; then - TARGET_STATUS="ASSIGNED" - else - TARGET_STATUS="Code Review" - fi - - echo "Transitioning $ISSUE_KEY to '$TARGET_STATUS'..." - if transition_issue "$ISSUE_KEY" "$TARGET_STATUS"; then - echo " Transition successful" - else - echo " Transition failed or not available" + # Transition issue to appropriate status based on configured mapping + if [ -n "${JIRA_AGENT_TARGET_STATUS:-}" ]; then + PROJECT_PREFIX=$(echo "$ISSUE_KEY" | cut -d'-' -f1) + TARGET_STATUS=$(echo "$JIRA_AGENT_TARGET_STATUS" | jq -r --arg prefix "$PROJECT_PREFIX" '.[$prefix] // empty') + if [ -n "$TARGET_STATUS" ]; then + echo "Transitioning $ISSUE_KEY to '$TARGET_STATUS'..." + if transition_issue "$ISSUE_KEY" "$TARGET_STATUS"; then + echo " Transition successful" + else + echo " Transition failed or not available" + fi + fi fi - # Set assignee to hypershift-team automation (Cloud requires accountId, look it up by display name) - echo "Looking up accountId for 'hypershift-team automation'..." - ASSIGNEE_ACCOUNT_ID=$(curl -s -G \ - "https://redhat.atlassian.net/rest/api/3/user/search" \ - -H "Authorization: Basic $JIRA_AUTH" \ - --data-urlencode "query=hypershift-automation" \ - | jq -r '[.[] | select(.displayName == "hypershift-team automation")] | .[0].accountId // empty') - if [ -n "$ASSIGNEE_ACCOUNT_ID" ]; then - echo "Setting assignee to account ID '${ASSIGNEE_ACCOUNT_ID}'..." - ASSIGNEE_RESPONSE=$(set_assignee "$ISSUE_KEY" "$ASSIGNEE_ACCOUNT_ID") - else - echo " Warning: Could not find accountId for 'hypershift-team automation', skipping assignee" - ASSIGNEE_RESPONSE="skipped + # Set assignee if configured + if [ -n "${JIRA_AGENT_ASSIGNEE:-}" ]; then + echo "Looking up accountId for '${JIRA_AGENT_ASSIGNEE}'..." + ASSIGNEE_ACCOUNT_ID=$(curl -s -G \ + "${JIRA_BASE_URL}/rest/api/3/user/search" \ + -H "Authorization: Basic $JIRA_AUTH" \ + --data-urlencode "query=${JIRA_AGENT_ASSIGNEE}" \ + | jq -r '[.[] | select(.displayName | test("'"${JIRA_AGENT_ASSIGNEE}"'"; "i"))] | .[0].accountId // empty') + if [ -n "$ASSIGNEE_ACCOUNT_ID" ]; then + echo "Setting assignee to account ID '${ASSIGNEE_ACCOUNT_ID}'..." + ASSIGNEE_RESPONSE=$(set_assignee "$ISSUE_KEY" "$ASSIGNEE_ACCOUNT_ID") + else + echo " Warning: Could not find accountId for '${JIRA_AGENT_ASSIGNEE}', skipping assignee" + ASSIGNEE_RESPONSE="skipped 200" - fi - HTTP_CODE=$(echo "$ASSIGNEE_RESPONSE" | tail -1) - if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then - echo " Assignee set successfully" - else - echo " Warning: Failed to set assignee (HTTP $HTTP_CODE)" + fi + HTTP_CODE=$(echo "$ASSIGNEE_RESPONSE" | tail -1) + if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then + echo " Assignee set successfully" + else + echo " Warning: Failed to set assignee (HTTP $HTTP_CODE)" + fi fi fi - PROCESSED_COUNT=$((PROCESSED_COUNT + 1)) - echo "$ISSUE_KEY $TIMESTAMP $PR_URL SUCCESS" >> "$STATE_FILE" + if [ "${ISSUE_SUCCESS:-true}" = true ]; then + PROCESSED_COUNT=$((PROCESSED_COUNT + 1)) + echo "$ISSUE_KEY $TIMESTAMP $PR_URL SUCCESS" >> "$STATE_FILE" + else + FAILED_COUNT=$((FAILED_COUNT + 1)) + echo "$ISSUE_KEY $TIMESTAMP - FAILED" >> "$STATE_FILE" + fi else # Log failure but don't mark as processed (will be retried next run) - echo "❌ Failed to process $ISSUE_KEY" + echo "Failed to process $ISSUE_KEY" echo "Error output (last 20 lines):" tail -20 "/tmp/claude-${ISSUE_KEY}-output.log" FAILED_COUNT=$((FAILED_COUNT + 1)) diff --git a/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-ref.metadata.json b/ci-operator/step-registry/jira-agent/process/jira-agent-process-ref.metadata.json similarity index 72% rename from ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-ref.metadata.json rename to ci-operator/step-registry/jira-agent/process/jira-agent-process-ref.metadata.json index 7b678563c9443..1b0e52b803db0 100644 --- a/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-ref.metadata.json +++ b/ci-operator/step-registry/jira-agent/process/jira-agent-process-ref.metadata.json @@ -1,5 +1,5 @@ { - "path": "hypershift/jira-agent/report/hypershift-jira-agent-report-ref.yaml", + "path": "jira-agent/process/jira-agent-process-ref.yaml", "owners": { "approvers": [ "bryan-cox", diff --git a/ci-operator/step-registry/jira-agent/process/jira-agent-process-ref.yaml b/ci-operator/step-registry/jira-agent/process/jira-agent-process-ref.yaml new file mode 100644 index 0000000000000..f8286dc731a8a --- /dev/null +++ b/ci-operator/step-registry/jira-agent/process/jira-agent-process-ref.yaml @@ -0,0 +1,120 @@ +ref: + as: jira-agent-process + from: claude-ai-helpers + commands: jira-agent-process-commands.sh + timeout: 14400s + env: + - name: CLAUDE_CODE_USE_VERTEX + default: "1" + documentation: |- + Enable Vertex AI for Claude Code. + - name: CLOUD_ML_REGION + default: "global" + documentation: |- + Google Cloud region for Vertex AI. + - name: ANTHROPIC_VERTEX_PROJECT_ID + default: "itpc-gcp-hybrid-pe-eng-claude" + documentation: |- + Google Cloud project ID for Vertex AI authentication. + - name: GOOGLE_APPLICATION_CREDENTIALS + default: "/var/run/claude-code-service-account/claude-prow" + documentation: |- + Path to the Google Cloud service account JSON key file for Vertex AI authentication. + - name: JIRA_AGENT_FORK_REPO + default: "" + documentation: |- + Required. Fork repository in org/repo format (e.g., "my-team/my-repo"). + The agent clones this repo and pushes branches to it. + - name: JIRA_AGENT_UPSTREAM_REPO + default: "" + documentation: |- + Required. Upstream repository in org/repo format (e.g., "openshift/my-repo"). + The agent creates pull requests against this repo. + - name: JIRA_AGENT_JQL + default: "" + documentation: |- + Required. JQL query for finding Jira issues to process. + Example: 'project = MYPROJ AND resolution = Unresolved AND status in (New, "To Do") AND labels = issue-for-agent AND labels != agent-processed' + - name: JIRA_AGENT_TARGET_STATUS + default: "" + documentation: |- + Optional JSON map of Jira project prefix to target status after processing. + Example: '{"OCPBUGS":"ASSIGNED","CNTRLPLANE":"Code Review"}' + Leave empty to skip status transitions. + - name: JIRA_AGENT_ASSIGNEE + default: "" + documentation: |- + Optional display name to search for when setting assignee on processed issues. + Example: "my-automation-user" + Leave empty to skip assignee updates. + - name: JIRA_AGENT_UPSTREAM_INSTALLATION_ID_KEY + default: "o-h-installation-id" + documentation: |- + Key name in the Vault secret for the upstream GitHub App installation ID. + - name: JIRA_AGENT_FORK_INSTALLATION_ID_KEY + default: "installation-id" + documentation: |- + Key name in the Vault secret for the fork GitHub App installation ID. + - name: JIRA_AGENT_EXTRA_PLUGIN_COMMANDS + default: "" + documentation: |- + Optional newline-separated Claude plugin install commands to run after + the base ai-helpers marketplace is added. Example: + claude plugin install utils@ai-helpers + claude plugin install golang@ai-helpers + - name: JIRA_AGENT_TOOL_SETUP_SCRIPT + default: "" + documentation: |- + Optional inline shell commands to install project-specific tools. + Example: "GOFLAGS='' go install golang.org/x/tools/gopls@v0.21.0" + - name: JIRA_AGENT_REVIEW_LANGUAGE + default: "go" + documentation: |- + Language for the code-review plugin (Phase 2). + - name: JIRA_AGENT_REVIEW_PROFILE + default: "" + documentation: |- + Optional profile name for the code-review plugin (Phase 2). + - name: JIRA_AGENT_SLACK_EMOJI + default: ":robot:" + documentation: |- + Slack emoji used in notification messages. + - name: JIRA_AGENT_ISSUE_KEY + default: "" + documentation: |- + Optional override to process a specific Jira issue instead of querying. + When set (e.g., "MYPROJ-123"), skips the JQL query and processes only this issue. + - name: MULTISTAGE_PARAM_OVERRIDE_JIRA_AGENT_ISSUE_KEY + default: "" + documentation: |- + Gangway API override for JIRA_AGENT_ISSUE_KEY. + - name: JIRA_AGENT_MAX_ISSUES + default: "1" + documentation: |- + Maximum number of Jira issues to process per run. + - name: CLAUDE_MODEL + default: "claude-opus-4-6" + documentation: |- + Claude model to use for processing Jira issues. + - name: JIRA_BASE_URL + default: "https://redhat.atlassian.net" + documentation: |- + Base URL for the Jira instance. + resources: + requests: + cpu: 500m + memory: 1Gi + credentials: + - namespace: test-credentials + name: hypershift-team-claude-prow + mount_path: /var/run/claude-code-service-account + documentation: |- + Generic process step for the Jira agent periodic job. + This step runs a four-phase pipeline for each issue: + Phase 1 - Solve: Runs /jira-solve to implement changes, commit, and push the branch + Phase 2 - Review: Runs /code-review:pre-commit-review for code quality (read-only) + Phase 3 - Fix: Addresses review findings by editing code, committing, and pushing fixes + Phase 4 - PR Creation: Creates a draft PR via gh CLI after review is complete + + Required env vars: JIRA_AGENT_FORK_REPO, JIRA_AGENT_UPSTREAM_REPO, JIRA_AGENT_JQL + Teams should override the credential secret name in their wrapper workflow. diff --git a/ci-operator/step-registry/jira-agent/report/OWNERS b/ci-operator/step-registry/jira-agent/report/OWNERS new file mode 100644 index 0000000000000..ff943340794d2 --- /dev/null +++ b/ci-operator/step-registry/jira-agent/report/OWNERS @@ -0,0 +1,12 @@ +approvers: + - bryan-cox + - csrwng + - celebdor + - enxebre + - sjenning +reviewers: + - bryan-cox + - csrwng + - celebdor + - enxebre + - sjenning diff --git a/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-commands.sh b/ci-operator/step-registry/jira-agent/report/jira-agent-report-commands.sh old mode 100755 new mode 100644 similarity index 95% rename from ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-commands.sh rename to ci-operator/step-registry/jira-agent/report/jira-agent-report-commands.sh index ed1bebcd86cea..8c090fc24716b --- a/ci-operator/step-registry/hypershift/jira-agent/report/hypershift-jira-agent-report-commands.sh +++ b/ci-operator/step-registry/jira-agent/report/jira-agent-report-commands.sh @@ -58,7 +58,7 @@ format_cost() { sum_costs() { local a=${1:-0} local b=${2:-0} - awk "BEGIN {printf \"%.6f\", $a + $b}" 2>/dev/null || echo "0" + awk -v a="$a" -v b="$b" 'BEGIN {printf "%.6f", (a + b)}' 2>/dev/null || echo "0" } # HTML-escape a string @@ -95,6 +95,8 @@ format_duration() { fi } +JIRA_BASE_URL="${JIRA_BASE_URL:-https://redhat.atlassian.net}" + # Build issue rows for summary table and detail sections SUMMARY_ROWS="" DETAIL_SECTIONS="" @@ -199,14 +201,14 @@ while IFS= read -r line; do # Build per-model breakdown rows from aggregated model_usage across phases MODEL_BREAKDOWN_ROWS="" - MODEL_FILES="" + MODEL_FILES_ARR=() for phase_key in solve review fix pr; do tf="${SHARED_DIR}/claude-${ISSUE_KEY}-${phase_key}-tokens.json" if [ -f "$tf" ]; then - MODEL_FILES="$MODEL_FILES $tf" + MODEL_FILES_ARR+=("$tf") fi done - if [ -n "$MODEL_FILES" ]; then + if [ ${#MODEL_FILES_ARR[@]} -gt 0 ]; then MODEL_BREAKDOWN=$(jq -s ' [.[].model_usage // {} | to_entries[]] | group_by(.key) @@ -220,7 +222,7 @@ while IFS= read -r line; do | sort_by(.model) | .[] | "\(.model)|\(.input)|\(.output)|\(.cache_read)|\(.cache_create)" - ' $MODEL_FILES 2>/dev/null || echo "") + ' "${MODEL_FILES_ARR[@]}" 2>/dev/null || echo "") if [ -n "$MODEL_BREAKDOWN" ]; then MODEL_BREAKDOWN_ROWS="<tr><td colspan=\"7\" style=\"background:#f0f0f0; font-size:0.85em; color:#666; padding:0.3em 1em;\"><em>Per-model breakdown</em></td></tr>" while IFS='|' read -r M_NAME M_INPUT M_OUTPUT M_CACHE_READ M_CACHE_CREATE; do @@ -256,11 +258,11 @@ while IFS= read -r line; do fi # Summary table row - SUMMARY_ROWS="${SUMMARY_ROWS}<tr><td><a href=\"https://redhat.atlassian.net/browse/${ISSUE_KEY}\">${ISSUE_KEY}</a></td><td>${ISSUE_TIMESTAMP}</td><td><span class=\"status ${STATUS_CLASS}\">${STATUS_LABEL}</span></td><td>${PR_LINK}</td><td>${ISSUE_COST}</td></tr>" + SUMMARY_ROWS="${SUMMARY_ROWS}<tr><td><a href=\"${JIRA_BASE_URL}/browse/${ISSUE_KEY}\">${ISSUE_KEY}</a></td><td>${ISSUE_TIMESTAMP}</td><td><span class=\"status ${STATUS_CLASS}\">${STATUS_LABEL}</span></td><td>${PR_LINK}</td><td>${ISSUE_COST}</td></tr>" DETAIL_SECTIONS="${DETAIL_SECTIONS} <div class=\"issue-card\"> - <h2><a href=\"https://redhat.atlassian.net/browse/${ISSUE_KEY}\">${ISSUE_KEY}</a> <span class=\"status ${STATUS_CLASS}\">${STATUS_LABEL}</span></h2> + <h2><a href=\"${JIRA_BASE_URL}/browse/${ISSUE_KEY}\">${ISSUE_KEY}</a> <span class=\"status ${STATUS_CLASS}\">${STATUS_LABEL}</span></h2> ${TOKEN_TABLE} <h3>Phase 1: Solve</h3> diff --git a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.metadata.json b/ci-operator/step-registry/jira-agent/report/jira-agent-report-ref.metadata.json similarity index 71% rename from ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.metadata.json rename to ci-operator/step-registry/jira-agent/report/jira-agent-report-ref.metadata.json index 22d12984f05fb..e2b373761e895 100644 --- a/ci-operator/step-registry/hypershift/jira-agent/process/hypershift-jira-agent-process-ref.metadata.json +++ b/ci-operator/step-registry/jira-agent/report/jira-agent-report-ref.metadata.json @@ -1,5 +1,5 @@ { - "path": "hypershift/jira-agent/process/hypershift-jira-agent-process-ref.yaml", + "path": "jira-agent/report/jira-agent-report-ref.yaml", "owners": { "approvers": [ "bryan-cox", diff --git a/ci-operator/step-registry/jira-agent/report/jira-agent-report-ref.yaml b/ci-operator/step-registry/jira-agent/report/jira-agent-report-ref.yaml new file mode 100644 index 0000000000000..e8accc95ee06a --- /dev/null +++ b/ci-operator/step-registry/jira-agent/report/jira-agent-report-ref.yaml @@ -0,0 +1,17 @@ +ref: + as: jira-agent-report + from: claude-ai-helpers + commands: jira-agent-report-commands.sh + env: + - name: JIRA_BASE_URL + default: "https://redhat.atlassian.net" + documentation: |- + Base URL for the Jira instance. Used for linking to issues in the report. + resources: + requests: + cpu: 100m + memory: 256Mi + documentation: |- + Generates an HTML report from the jira-agent processing output. + Parses stream-json output from all phases (solve, review, fix, PR) + and produces a readable report in ${ARTIFACT_DIR}. diff --git a/ci-operator/step-registry/jira-agent/setup/OWNERS b/ci-operator/step-registry/jira-agent/setup/OWNERS new file mode 100644 index 0000000000000..ff943340794d2 --- /dev/null +++ b/ci-operator/step-registry/jira-agent/setup/OWNERS @@ -0,0 +1,12 @@ +approvers: + - bryan-cox + - csrwng + - celebdor + - enxebre + - sjenning +reviewers: + - bryan-cox + - csrwng + - celebdor + - enxebre + - sjenning diff --git a/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-commands.sh b/ci-operator/step-registry/jira-agent/setup/jira-agent-setup-commands.sh old mode 100755 new mode 100644 similarity index 51% rename from ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-commands.sh rename to ci-operator/step-registry/jira-agent/setup/jira-agent-setup-commands.sh index 94cc7c5d99139..145cd3035294f --- a/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-commands.sh +++ b/ci-operator/step-registry/jira-agent/setup/jira-agent-setup-commands.sh @@ -1,10 +1,16 @@ #!/bin/bash set -euo pipefail -echo "=== HyperShift Jira Agent Setup ===" +echo "=== Jira Agent Setup ===" # Verify Claude Code is available (Vertex AI authentication is handled via GOOGLE_APPLICATION_CREDENTIALS env var) echo "Verifying Claude Code CLI..." claude --version || { echo "ERROR: Claude Code CLI not found"; exit 1; } +echo "Verifying Vertex AI credentials..." +if [ -z "${GOOGLE_APPLICATION_CREDENTIALS:-}" ] || [ ! -r "${GOOGLE_APPLICATION_CREDENTIALS}" ]; then + echo "ERROR: GOOGLE_APPLICATION_CREDENTIALS is not set or not readable" + exit 1 +fi + echo "Setup complete" diff --git a/ci-operator/step-registry/jira-agent/setup/jira-agent-setup-ref.metadata.json b/ci-operator/step-registry/jira-agent/setup/jira-agent-setup-ref.metadata.json new file mode 100644 index 0000000000000..7615067e833c7 --- /dev/null +++ b/ci-operator/step-registry/jira-agent/setup/jira-agent-setup-ref.metadata.json @@ -0,0 +1,19 @@ +{ + "path": "jira-agent/setup/jira-agent-setup-ref.yaml", + "owners": { + "approvers": [ + "bryan-cox", + "csrwng", + "celebdor", + "enxebre", + "sjenning" + ], + "reviewers": [ + "bryan-cox", + "csrwng", + "celebdor", + "enxebre", + "sjenning" + ] + } +} \ No newline at end of file diff --git a/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.yaml b/ci-operator/step-registry/jira-agent/setup/jira-agent-setup-ref.yaml similarity index 67% rename from ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.yaml rename to ci-operator/step-registry/jira-agent/setup/jira-agent-setup-ref.yaml index 7aa6338f82932..60c5d35fe39de 100644 --- a/ci-operator/step-registry/hypershift/jira-agent/setup/hypershift-jira-agent-setup-ref.yaml +++ b/ci-operator/step-registry/jira-agent/setup/jira-agent-setup-ref.yaml @@ -1,7 +1,7 @@ ref: - as: hypershift-jira-agent-setup + as: jira-agent-setup from: claude-ai-helpers - commands: hypershift-jira-agent-setup-commands.sh + commands: jira-agent-setup-commands.sh env: - name: CLAUDE_CODE_USE_VERTEX default: "1" @@ -28,10 +28,7 @@ ref: name: hypershift-team-claude-prow mount_path: /var/run/claude-code-service-account documentation: |- - Setup step for the HyperShift Jira agent periodic job. - This step: - - Clones the HyperShift repository - - Configures git credentials for creating commits - - Sets up GitHub CLI authentication - - Verifies Claude Code CLI is available - - Uses Vertex AI for Claude authentication via GCP service account + Generic setup step for the Jira agent periodic job. + Verifies Claude Code CLI is available. + Uses Vertex AI for Claude authentication via GCP service account. + Teams should override the credential secret name in their wrapper workflow.