$npx -y skills add opendatahub-io/ai-helpers --skill jira-autofix-cve-resolveUse this skill to orchestrate CVE remediation for a Jira Vulnerability ticket. Resolves affected repositories via component-repository-mappings.json, then for each repo and branch: scans, fixes, verifies, and creates PRs. Handles upstream-to-downstream ordering, fork fallback, ex
| 1 | # Skill: CVE Resolve Orchestrator |
| 2 | |
| 3 | You orchestrate CVE remediation for a Jira Vulnerability ticket. You call |
| 4 | specialized CVE skills in sequence for each affected repository and branch. |
| 5 | You NEVER write fix code yourself — you only parse context, resolve repos, |
| 6 | route to skills, create PRs, and write the final verdict. |
| 7 | |
| 8 | This skill replaces `jira-autofix-resolve` when the ticket is a Vulnerability |
| 9 | with the `SecurityTracking` label. |
| 10 | |
| 11 | ## Step 1: Parse ticket context |
| 12 | |
| 13 | Read `.autofix-context/ticket.json`. Extract: |
| 14 | |
| 15 | - **CVE ID** — from summary: `CVE-YYYY-XXXXX ...` |
| 16 | - **Container** — from summary: `CVE-YYYY-XXXXX <container>: <package>: <desc>` |
| 17 | - **Package** — the token after the first colon |
| 18 | - **Component** — from the Jira `components` field |
| 19 | - **CVSS score** — from `customfield_10859` |
| 20 | - **Severity** — from `customfield_10840` |
| 21 | - **Jira key** — e.g., `RHOAIENG-17794` |
| 22 | |
| 23 | Parse container and package with explicit extraction: |
| 24 | |
| 25 | ```bash |
| 26 | SUMMARY=$(jq -r '.fields.summary' .autofix-context/ticket.json) |
| 27 | CVE_ID=$(echo "$SUMMARY" | grep -oP 'CVE-[0-9]+-[0-9]+') |
| 28 | CONTAINER=$(echo "$SUMMARY" | grep -oP '(?<=CVE-[0-9]+-[0-9]+ )[\w/.-]+(?=:)') |
| 29 | PACKAGE=$(echo "$SUMMARY" | grep -oP '(?<=: )[\w.@/_-]+(?=:)') |
| 30 | ``` |
| 31 | |
| 32 | Check for ignore patterns in ticket comments: |
| 33 | - `cve-automation-ignore`, `skip-cve-automation`, `ignore-cve-automation`, `automation-ignore-cve` |
| 34 | |
| 35 | If found → set verdict to `no_changes` with reason "ticket has automation-ignore comment", skip. |
| 36 | |
| 37 | ## Step 2: Resolve repositories |
| 38 | |
| 39 | Load `component-repository-mappings.json`. |
| 40 | |
| 41 | **Container-scoped resolution (preferred):** |
| 42 | |
| 43 | 1. Search `.components[COMPONENT].repos[]` for a repo whose `.containers[]` |
| 44 | includes the container name from the summary |
| 45 | 2. If matched, only these repos get PRs |
| 46 | 3. If no container extracted from summary, log a warning and process all repos |
| 47 | |
| 48 | **Fallback — all repos:** |
| 49 | |
| 50 | If no container matched, process all repos in the component. The scan step |
| 51 | filters out repos where the CVE doesn't exist. |
| 52 | |
| 53 | **Determine target branches per repo:** |
| 54 | |
| 55 | ```bash |
| 56 | TARGET_BRANCHES=$(jq -r '[.default_branch] + .active_release_branches | unique | .[]' <<< "$REPO_CONFIG") |
| 57 | ``` |
| 58 | |
| 59 | ## State Persistence |
| 60 | |
| 61 | Before processing repos, write the full execution plan to disk. Re-read |
| 62 | this file before every step to survive context compression: |
| 63 | |
| 64 | ```bash |
| 65 | # Write execution state (survives context compression) |
| 66 | STATE_FILE="autofix-output/orchestrator-state.json" |
| 67 | mkdir -p autofix-output |
| 68 | jq -n \ |
| 69 | --arg cve_id "$CVE_ID" \ |
| 70 | --arg package "$PACKAGE" \ |
| 71 | --arg jira_key "$JIRA_KEY" \ |
| 72 | --argjson repos "$REPOS_JSON" \ |
| 73 | '{ |
| 74 | cve_id: $cve_id, |
| 75 | package: $package, |
| 76 | jira_key: $jira_key, |
| 77 | repos: $repos, |
| 78 | processed: [], |
| 79 | skipped: [], |
| 80 | prs_created: [], |
| 81 | current_repo: null, |
| 82 | current_branch: null |
| 83 | }' > "$STATE_FILE" |
| 84 | ``` |
| 85 | |
| 86 | After each repo/branch completes, update the state file: |
| 87 | |
| 88 | ```bash |
| 89 | # Mark branch as processed |
| 90 | jq --arg repo "$REPO_FULL" --arg branch "$TARGET_BRANCH" --arg verdict "$VERDICT" \ |
| 91 | '.processed += [{"repo": $repo, "branch": $branch, "verdict": $verdict}] | |
| 92 | .current_repo = null | .current_branch = null' \ |
| 93 | "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE" |
| 94 | ``` |
| 95 | |
| 96 | Before starting any step, re-read state from disk (not from memory): |
| 97 | |
| 98 | ```bash |
| 99 | CVE_ID=$(jq -r '.cve_id' "$STATE_FILE") |
| 100 | PACKAGE=$(jq -r '.package' "$STATE_FILE") |
| 101 | JIRA_KEY=$(jq -r '.jira_key' "$STATE_FILE") |
| 102 | ``` |
| 103 | |
| 104 | ## Step 3: Process each repo (upstream first) |
| 105 | |
| 106 | Sort repos by type: `upstream` → `midstream` → `downstream`. |
| 107 | |
| 108 | **Parallel repo processing:** |
| 109 | |
| 110 | When multiple repos are resolved (e.g., upstream + downstream), process |
| 111 | them in parallel by launching each as a background subagent: |
| 112 | |
| 113 | - Upstream repo agent (background=true) |
| 114 | - Downstream repo agent (background=true) |
| 115 | |
| 116 | Each repo agent processes its branches sequentially (branches share |
| 117 | the clone). The orchestrator waits for all repo agents to complete, |
| 118 | then merges their JSON results into the final verdict. |
| 119 | |
| 120 | Monitor progress by checking for expected output files: |
| 121 | |
| 122 | ```bash |
| 123 | # Check if repo processing is complete |
| 124 | for REPO in "${REPOS[@]}"; do |
| 125 | REPO_STATE="autofix-output/repo-${REPO//\//-}-result.json" |
| 126 | if [ -f "$REPO_STATE" ]; then |
| 127 | echo "${REPO} complete" |
| 128 | else |
| 129 | echo "${REPO} still processing" |
| 130 | fi |
| 131 | done |
| 132 | ``` |
| 133 | |
| 134 | For each repo: |
| 135 | |
| 136 | ### 3a: Clone and check write access |
| 137 | |
| 138 | ```bash |
| 139 | REPO_DIR="/tmp/${REPO_ORG}/${REPO_NAME}" |
| 140 | mkdir -p "/tmp/${REPO_ORG}" |
| 141 | gh repo clone "${REPO_FULL}" "$REPO_DIR" -- --no-single-branch || \ |
| 142 | git clone "https://github.com/${REPO_FULL}.git" "$REPO_ |