diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 14efd96..f5016f9 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -5,14 +5,14 @@
},
"metadata": {
"description": "Orchestrator skill for RHDH plugin development - onboard, update, and maintain plugins in the Extensions Catalog",
- "version": "0.6.1"
+ "version": "0.7.0"
},
"plugins": [
{
"name": "rhdh",
"source": "./",
"description": "Skills for RHDH plugin lifecycle management",
- "version": "0.6.1",
+ "version": "0.7.0",
"strict": true
}
]
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index ec8a1cd..f4ba7f1 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "rhdh",
"description": "All-in-one toolkit for Red Hat Developer Hub (RHDH). Covers plugin development, overlay management, environment setup, version compatibility, CI/CD, and RHDH ecosystem navigation.",
- "version": "0.6.1",
+ "version": "0.7.0",
"author": {
"name": "RHDH Store Manager"
},
diff --git a/README.md b/README.md
index 13766c3..46ecd67 100644
--- a/README.md
+++ b/README.md
@@ -94,6 +94,18 @@ Track work across the four RHDH Jira projects.
- **[to-issue](./skills/rhdh-jira/references/to-issue.md)** — Create a Story, Task, Bug, or Spike with automatic type inference. Grills on implementation details and story points.
- **[update-jira-status](./skills/rhdh-jira/references/update-jira-status.md)** — Update an issue with session progress. Detects the related issue, adds a status comment, proposes transitions, and checks upward cascade to parent Epic/Feature.
+### PR Workflow
+
+Automate the full PR lifecycle — build, changeset, commit, push, and create — for plugin monorepos.
+
+- **[raise-pr](./skills/raise-pr/SKILL.md)** — Full PR workflow for `rhdh-plugins` and `community-plugins`: detect workspace from staged changes, run build/validation, generate changesets, commit with sign-off, push, and create the GitHub PR. Auto-detects which repo you're in. Supports `--a` auto-approve mode to skip all approval gates. Accepts an optional Jira key or URL to link the PR — adds Web Link, comment, and transitions the issue to Review.
+
+### Bug Fix
+
+Reproduce, diagnose, fix, and PR RHDH plugin bugs from Jira tickets with automated Playwright-based before/after screen recordings.
+
+- **[bug-fix](./skills/bug-fix/SKILL.md)** — End-to-end bug fix workflow: fetch Jira issue, map component to workspace, write Playwright reproduction test with video recording, diagnose root cause, apply fix, verify, and create PR with before/after recordings embedded. Chains into `raise-pr` for the full PR lifecycle including post-PR Jira updates.
+
### PR Review
- **[rhdh-pr-review](./skills/rhdh-pr-review/SKILL.md)** — PR code review with inline comments (GitHub, GitLab planned) and live cluster testing for rhdh-operator PRs. Layered architecture: fetch → analyze → post.
diff --git a/pyproject.toml b/pyproject.toml
index f76fce1..e50f84b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "rhdh-skill"
-version = "0.6.1"
+version = "0.7.0"
description = "Claude Code skill for RHDH plugin development"
readme = "README.md"
license = "Apache-2.0"
diff --git a/skills/bug-fix/SKILL.md b/skills/bug-fix/SKILL.md
new file mode 100644
index 0000000..2a73780
--- /dev/null
+++ b/skills/bug-fix/SKILL.md
@@ -0,0 +1,345 @@
+---
+name: bug-fix
+description: >
+ Reproduce, diagnose, fix, and PR RHDH plugin bugs from Jira tickets using
+ Playwright e2e tests with before/after screen recordings. Accepts a Jira key
+ (RHDHBUGS-1934), Jira URL (redhat.atlassian.net/browse/...), or a request to
+ "fix this bug", "reproduce and fix", "/bug-fix". Chains into raise-pr for the
+ full PR lifecycle including post-PR Jira comment.
+---
+
+
+
+
+As the very first action when the skill is invoked, echo a skill entry banner to the terminal:
+```
+echo "================ Using Bug Fix Skill ==========="
+```
+This must happen before any other work (reading references, MCP calls, etc.).
+
+
+
+The reproduction test (`_repro-.test.ts`) is a diagnostic tool, not a deliverable. It is deleted before staging. It must never appear in the PR.
+
+
+
+Do not hardcode workspace internals. Discover each workspace's e2e infrastructure at runtime by reading its `playwright.config.ts`, `e2e-tests/utils/`, and `plugins/*/src/translations/ref.ts`. The `references/workspace-map.md` maps Jira components to workspace directories, but everything else is discovered dynamically.
+
+
+
+Every bug fix PR with a UI change MUST include before/after screen recordings. This is NON-NEGOTIABLE and cannot be skipped, deferred, or worked around. The recordings prove the bug existed and the fix resolves it.
+
+Enforcement rules:
+- The reproduction test MUST be written and run BEFORE any fix is applied (Steps 3-4 happen before Step 5). Violating this order means there is no "before" state to record.
+- The repro test MUST create its own browser context with `recordVideo` — NEVER use workspace bootstrap helpers (e.g., `bootstrapLightspeedE2ePage`) as they do not enable video recording.
+- If video files are not present in `e2e-tests/_repro-artifacts/` at Step 8, STOP and go back to capture them. Do NOT proceed to PR creation without recordings.
+- If the Playwright `context.close()` call is missing, the video file will be incomplete — always close the context.
+
+
+
+NEVER apply the code fix before Steps 3 and 4 are complete. The correct order is:
+1. Write repro test (Step 3)
+2. Run repro test — it must FAIL (bug confirmed)
+3. Capture "before" video (Step 4)
+4. ONLY THEN apply the fix (Step 5)
+
+If you find yourself wanting to fix first and test after, STOP — you are violating the step order. The "before" recording cannot be captured retroactively.
+
+
+
+Before executing each numbered Step, echo a clearly visible banner to the terminal so the user can track progress — even if the step's actual work is done via MCP tools or file reads rather than shell commands:
+```
+echo "================ Step N — ==========="
+```
+This applies to ALL steps including Step 1. Run the echo command in a Shell tool call before doing anything else for that step.
+
+
+
+Before running any Playwright test, check whether the dev-server port (from `playwright.config.ts`) is already in use. If it is, kill the process occupying it:
+```
+lsof -ti: | xargs kill -9 2>/dev/null || true
+```
+A stale dev server from a prior session will cause the test to connect to the wrong app and time out.
+
+
+
+Before running any Playwright test, ensure the system file descriptor limit is raised and always use `required_permissions: ["all"]` on the shell command to avoid sandbox restrictions on browser launch:
+```
+ulimit -n 65536 2>/dev/null || true
+```
+Without this, webpack's file watcher (Watchpack) may hit `EMFILE: too many open files` and crash Chrome/Chromium.
+
+
+
+
+## Prerequisites
+
+- **`gh` CLI** — GitHub CLI must be installed and authenticated (`gh auth status` should show logged in). Install: https://cli.github.com/
+- **Jira MCP** — The Atlassian Rovo MCP server must be configured in Cursor for Jira comment updates. Setup guide: https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/
+ - If not configured, the skill will skip Jira updates and log a warning.
+- Working checkout of `rhdh-plugins` (or `community-plugins`)
+- `yarn` available on PATH
+- `ffmpeg` available on PATH (for video conversion; fall back to raw `.webm` if absent)
+
+---
+
+## Step 1 — Fetch Jira issue and parse details
+
+Read `references/workspace-map.md` for the Jira component-to-workspace mapping.
+
+1. Parse the Jira reference from the user's input. Follow the parsing rules in `raise-pr/references/jira-input.md`:
+ - Bare key: `RHDHBUGS-1934`
+ - Browse URL: `https://redhat.atlassian.net/browse/RHDHBUGS-1934`
+ - URL without scheme: `redhat.atlassian.net/browse/RHIDP-15252`
+2. Fetch the full issue details using the Jira REST API or MCP (`read_jira_issue`):
+ - Summary, description, steps to reproduce
+ - Component field (maps to workspace)
+ - Status (for post-PR transition)
+ - Attachments/screenshots (visual reference for reproduction)
+3. Store: `jira_key`, `jira_url`, `jira_summary`, `jira_description`, `jira_component`, `jira_status`
+
+**If the description has no clear steps to reproduce**: ask the user to provide reproduction steps before proceeding.
+
+---
+
+## Step 2 — Identify workspace and discover e2e infrastructure
+
+1. Map the Jira **Component** field to a workspace directory using `references/workspace-map.md`.
+ - If no component is set or the component is unknown: ask the user which workspace to target.
+2. Navigate to the workspace: `cd workspaces/`
+3. **Discover e2e infrastructure dynamically**:
+ - Read `playwright.config.ts` for port configuration, locale list, start commands, and `APP_MODE` support.
+ - Scan `e2e-tests/utils/` to discover available helper functions (translations, navigation, API mocking, accessibility).
+ - Read `plugins/*/src/translations/ref.ts` for translation key structure (used for i18n-safe selectors).
+ - Read `plugins/*/src/components/` to build a component-to-source-file map.
+4. Run `yarn install` if `node_modules` is missing or stale.
+
+**If the workspace has no `playwright.config.ts`**: fall back to a screenshot-only approach — skip video recording and use DOM assertions or manual screenshots instead.
+
+Read `references/e2e-patterns.md` for shared Playwright patterns across all rhdh-plugins workspaces.
+
+---
+
+## Step 3 — Write reproduction test with video recording
+
+Read `references/e2e-patterns.md` for test patterns and `references/video-recording.md` for video configuration.
+
+1. Create a temporary test file: `e2e-tests/_repro-.test.ts`
+ - The `_` prefix signals this file is temporary and should not be committed.
+2. The test must:
+ - Import workspace-specific helpers discovered in Step 2 **only for navigation/setup** (e.g., API mocking, translations).
+ - Use i18n-safe selectors (via translation keys) where available.
+ - **Always create its own browser context with video recording** — do NOT rely on workspace bootstrap helpers for the context, as they may not enable video. Use the `browser` fixture directly:
+ ```typescript
+ test('repro', async ({ browser }) => {
+ const context = await browser.newContext({
+ recordVideo: { dir: 'test-results/', size: { width: 1280, height: 720 } },
+ });
+ const page = await context.newPage();
+
+ // ... test steps using page ...
+
+ await context.close(); // finalizes the video file
+ });
+ ```
+ This guarantees video recording regardless of how the workspace's own e2e infrastructure manages contexts.
+ - **CRITICAL**: Do NOT use workspace-provided bootstrap/setup functions (like `bootstrapLightspeedE2ePage`, `setupE2eTest`, etc.) as the browser context source. These helpers create contexts WITHOUT video recording. You MUST call `browser.newContext({ recordVideo: ... })` yourself and then replicate only the mock setup from those helpers (API mocking, route handlers) on your custom page. Copy the mock calls — not the context creation.
+ - Encode the "steps to reproduce" from the Jira description as Playwright actions.
+ - Assert the **expected** behavior (the assertion should fail when the bug is present).
+3. **Pre-flight: kill stale dev server** — before running the test, ensure the dev-server port (read from `playwright.config.ts` `webServer.url`) is free:
+ ```
+ lsof -ti: | xargs kill -9 2>/dev/null || true
+ ```
+4. Run the test against the `en` locale in legacy mode. **Always** prefix with `ulimit -n 65536` and use `required_permissions: ["all"]` on the Shell tool call:
+ ```
+ ulimit -n 65536 && APP_MODE=legacy npx playwright test e2e-tests/_repro-.test.ts --project=en
+ ```
+5. The test should **fail** — confirming the bug is reproduced.
+
+**If the test passes** (bug not reproduced): re-read the Jira description, adjust the test, and retry. If still not reproducible after 2 attempts, report findings and ask the user for guidance.
+
+---
+
+## Step 4 — Capture "before" recording
+
+1. After the failed test run (Step 3), locate the video file in `test-results/`.
+ - Playwright saves videos at `test-results//video.webm`.
+2. Copy the video to a stable path:
+ ```
+ mkdir -p e2e-tests/_repro-artifacts
+ cp test-results/*/video.webm e2e-tests/_repro-artifacts/before-fix.webm
+ ```
+3. Store the path for later conversion (Step 7).
+
+**If no video file is found in `test-results/`**: the test likely used a bootstrap helper instead of a custom `recordVideo` context. Rewrite the test to use `browser.newContext({ recordVideo: ... })` directly, then re-run.
+
+---
+
+## Step 5 — Diagnose and fix
+
+1. **Capture diagnostic context** from the failing state (before applying any fix). Re-use the reproduction test or run a lightweight Playwright script to gather:
+
+ a. **Screenshot** of the buggy UI state:
+ ```typescript
+ await page.screenshot({ path: 'e2e-tests/_repro-artifacts/bug-state.png', fullPage: true });
+ ```
+ b. **DOM snapshot** of the target element:
+ ```typescript
+ const targetEl = page.locator('');
+ const domSnapshot = await targetEl.evaluate(el => el.outerHTML);
+ ```
+ c. **Computed styles** of the target element (capture properties relevant to the bug):
+ ```typescript
+ const styles = await targetEl.evaluate(el => {
+ const cs = window.getComputedStyle(el);
+ return { display: cs.display, overflow: cs.overflow, scrollbarWidth: cs.scrollbarWidth };
+ });
+ ```
+
+ Use the screenshot (read it as an image), DOM structure, and computed styles to identify the exact root cause before modifying any source files. This provides concrete runtime evidence rather than guessing from source alone.
+
+2. **Diagnose**: trace from the failing Playwright selector back to the source:
+ - Identify which React component renders the UI element under test.
+ - Read the component source code (`plugins/*/src/components/`).
+ - Cross-reference the captured DOM/styles with the component's render logic to pinpoint the root cause (e.g., MUI prop misconfiguration, missing state update, CSS issue, accessibility gap, i18n key mismatch).
+3. **Apply the fix** in the source code.
+4. **Validate**:
+ - `yarn tsc:full` — type check passes.
+ - `yarn test --watchAll=false` — unit tests pass.
+
+**Confidence gates** — ask the user before proceeding if:
+- Multiple possible root causes exist — present options and let the user choose.
+- The fix touches more than 3 files — show the plan and get approval.
+- The fix changes API surface or public types — this may need a minor version bump.
+
+---
+
+## Step 6 — Capture "after" recording
+
+1. Re-run the reproduction test (with `ulimit` and `required_permissions: ["all"]`):
+ ```
+ ulimit -n 65536 && APP_MODE=legacy npx playwright test e2e-tests/_repro-.test.ts --project=en
+ ```
+2. The test should **pass** — confirming the fix works.
+3. Copy the video:
+ ```
+ cp test-results/*/video.webm e2e-tests/_repro-artifacts/after-fix.webm
+ ```
+4. Optionally run in NFS mode as well to check for mode-specific regressions:
+ ```
+ APP_MODE=nfs npx playwright test e2e-tests/_repro-.test.ts --project=en
+ ```
+
+**If the test still fails after the fix**: re-examine the diagnosis and iterate.
+
+---
+
+## Step 7 — Convert videos for PR embedding
+
+Read `references/video-recording.md` for conversion details.
+
+1. Check if `ffmpeg` is available on PATH.
+2. **If available**: convert `.webm` to `.gif`:
+ ```
+ ffmpeg -i e2e-tests/_repro-artifacts/before-fix.webm -vf "fps=10,scale=800:-1" -loop 0 e2e-tests/_repro-artifacts/before-fix.gif
+ ffmpeg -i e2e-tests/_repro-artifacts/after-fix.webm -vf "fps=10,scale=800:-1" -loop 0 e2e-tests/_repro-artifacts/after-fix.gif
+ ```
+3. **If `ffmpeg` is not available**: keep the `.webm` files and note that they will be uploaded as PR comment attachments instead of inline GIFs.
+
+---
+
+## Step 8 — Clean up and create PR
+
+### 8.0 — Validate recordings exist [HARD GATE]
+
+Before proceeding with cleanup or PR creation, verify both recording files exist:
+
+```
+test -f e2e-tests/_repro-artifacts/before-fix.webm || { echo "ERROR: before-fix.webm missing — go back to Step 4"; exit 1; }
+test -f e2e-tests/_repro-artifacts/after-fix.webm || { echo "ERROR: after-fix.webm missing — go back to Step 6"; exit 1; }
+```
+
+If either file is missing, DO NOT proceed. Return to the relevant step (4 or 6) and capture the recording. This gate ensures the PR will always have visual evidence.
+
+### 8.1 — Delete temporary files
+
+Remove the reproduction test and artifacts — these must not appear in the PR:
+
+```
+rm e2e-tests/_repro-.test.ts
+rm -rf test-results/
+```
+
+Keep `e2e-tests/_repro-artifacts/` temporarily (needed for PR image upload).
+
+### 8.2 — Stage fix files
+
+Stage only the code fix (not the repro test or artifacts):
+
+```
+git add
+```
+
+### 8.3 — Chain into raise-pr
+
+Invoke `raise-pr --a` with the following caller context:
+
+| Field | Value |
+|-------|-------|
+| `jira_key` | The resolved Jira key from Step 1 |
+| `jira_url` | `https://redhat.atlassian.net/browse/` |
+| `jira_summary` | Issue summary from Step 1 |
+| `recordings` | `{ before: "e2e-tests/_repro-artifacts/before-fix.gif", after: "e2e-tests/_repro-artifacts/after-fix.gif" }` |
+| `pr_description_extra` | `### Root cause\n` |
+| `test_plan` | Auto-generated markdown checklist (see below) |
+
+**Generating the `test_plan`:**
+
+Build a markdown checklist of verification steps for the reviewer. Derive them from:
+1. **Jira steps-to-reproduce** — convert each step into a positive verification action (e.g., "Click Help menu" becomes "- [ ] Open the Help menu").
+2. **Expected behavior after fix** — add steps verifying the fix works (e.g., "- [ ] Verify scrollbar appears on hover").
+3. **Regression check** — add at least one step confirming nothing else broke (e.g., "- [ ] Verify other menus/display modes are unchanged").
+
+Example output:
+```
+- [ ] Open the dropdown menu with many items
+- [ ] Verify scrollbar is hidden by default
+- [ ] Hover over the menu content area
+- [ ] Verify scrollbar becomes visible on hover
+- [ ] Verify other dropdown menus are unaffected
+```
+
+`raise-pr` handles: repo detection, build, changeset, commit (with `Fixes:` trailer), push, PR creation (with `## UI before/after changes` and `## Test Plan`), and post-PR Jira comment.
+
+> `raise-pr` uploads the GIF files to the branch via GitHub Contents API and embeds the resulting `raw.githubusercontent.com` URLs directly in the PR description. No manual image upload or separate PR comment is needed.
+
+### 8.4 — Final cleanup
+
+After the PR is created, delete the artifacts directory:
+
+```
+rm -rf e2e-tests/_repro-artifacts/
+```
+
+---
+
+## When NOT to Use
+
+- **Backend-only bugs** — if the bug has no UI component, there is nothing to video-record. Use standard debugging and fix workflows instead.
+- **Bugs requiring live backend data** — if reproduction depends on real API responses that cannot be mocked via the workspace's e2e test infrastructure.
+- **Cross-workspace bugs** — if the fix requires changes across multiple workspaces, handle each workspace separately or use `raise-pr` directly.
+- **Non-RHDH Jira projects** — this skill's workspace mapping is specific to `rhdh-plugins` workspaces and RHDH Jira projects (RHIDP, RHDHBUGS, RHDHPLAN, RHDHSUPP).
+
+
+
+## Reference Index
+
+| Reference | Load when... |
+|-----------|-------------|
+| `references/workspace-map.md` | Always — at the start of every invocation (Step 1-2) |
+| `references/e2e-patterns.md` | When writing the reproduction test (Step 3) |
+| `references/video-recording.md` | When configuring video capture (Step 3) and converting videos (Step 7) |
+| `raise-pr/references/jira-input.md` | When parsing Jira keys/URLs (Step 1) — shared with raise-pr |
+| `raise-pr/references/repo-profiles.md` | Loaded by raise-pr during Step 8.3 chain |
+
+
diff --git a/skills/bug-fix/references/e2e-patterns.md b/skills/bug-fix/references/e2e-patterns.md
new file mode 100644
index 0000000..e8c616f
--- /dev/null
+++ b/skills/bug-fix/references/e2e-patterns.md
@@ -0,0 +1,196 @@
+# E2E Patterns: Shared Playwright Infrastructure
+
+Common patterns across all `rhdh-plugins` workspaces. Reference this when writing reproduction tests (Step 3).
+
+## Shared Architecture
+
+All workspaces with e2e tests follow the same structure:
+
+```
+workspaces//
+├── playwright.config.ts # Multi-locale, dual-mode config
+├── e2e-tests/
+│ ├── *.test.ts # Test files
+│ └── utils/
+│ ├── translations.ts # i18n helper
+│ ├── *Helpers.ts # Workspace-specific helpers
+│ ├── accessibility.ts # a11y testing
+│ └── localeSkip.ts # Locale skip logic
+├── app-config.yaml # Base Backstage config
+└── e2e-tests/test_yamls/ # Per-locale test configs
+```
+
+## Multi-Locale Setup
+
+All workspaces use the same 6 locales:
+
+```typescript
+const LOCALES = ['en', 'de', 'es', 'fr', 'it', 'ja'] as const;
+```
+
+Each locale gets its own frontend and backend port:
+
+```typescript
+const FRONTEND_PORT_BASE = 3000; // en=3000, de=3001, es=3002, ...
+const BACKEND_PORT_BASE = 7007; // en=7007, de=7008, es=7009, ...
+```
+
+**For reproduction tests**: always run against `en` only (fastest feedback):
+
+```
+APP_MODE=legacy npx playwright test e2e-tests/_repro-.test.ts --project=en
+```
+
+## Dual-Mode (APP_MODE)
+
+All workspaces support two frontend modes via the `APP_MODE` environment variable:
+
+- `legacy` (default) — uses `packages/app-legacy` or `yarn start:legacy`
+- `nfs` — uses the New Frontend System via `packages/app` or `yarn start`
+
+The `playwright.config.ts` reads this:
+
+```typescript
+const appMode = process.env.APP_MODE || 'legacy';
+const startCommand = appMode === 'legacy' ? 'yarn start:legacy' : 'yarn start';
+```
+
+**For reproduction**: run legacy first. After the fix, verify both modes.
+
+## Translation Helpers
+
+Most workspaces have a `getTranslations()` helper that loads the plugin's translation keys for the current locale. Use these for i18n-safe selectors:
+
+```typescript
+import { getTranslations, type InsightsMessages } from './utils/translations.js';
+
+test.beforeAll(async ({ browser }) => {
+ const translations = getTranslations(locale);
+ // Use translations.header.title instead of hardcoded "Adoption Insights"
+});
+```
+
+**Why**: hardcoded English strings break in non-`en` locales. Always use translation keys when available.
+
+## Common Playwright Selectors
+
+Prefer accessibility-first selectors:
+
+```typescript
+// Role-based (best)
+page.getByRole('button', { name: 'Submit' })
+page.getByRole('combobox')
+page.getByRole('listbox')
+page.getByRole('tab', { name: translations.tabs.overview })
+
+// Text-based (good for translated text)
+page.getByText(translations.header.dateRange.defaultLabel)
+
+// Test ID (fallback)
+page.getByTestId('date-range-select')
+
+// CSS selector (last resort)
+page.locator('.MuiSelect-root')
+```
+
+## Waiting Patterns
+
+```typescript
+// Wait for navigation to complete
+await page.waitForURL('**/adoption-insights');
+
+// Wait for network idle
+await page.waitForLoadState('networkidle');
+
+// Wait for specific element
+await expect(page.getByRole('heading', { name: title })).toBeVisible();
+
+// Custom wait for data flush
+await waitForDataFlush(); // workspace-specific helper
+```
+
+## MUI Component Patterns
+
+Many rhdh-plugins use Material-UI. Common interaction patterns:
+
+```typescript
+// MUI Select (dropdown)
+const select = page.getByText(translations.header.dateRange.defaultLabel).first();
+await select.click();
+const listbox = page.getByRole('listbox');
+await expect(listbox).toBeVisible();
+
+// MUI Select with keyboard
+await page.keyboard.press('ArrowDown');
+const focused = listbox.locator(':focus');
+await expect(focused).toHaveCount(1);
+await page.keyboard.press('Enter');
+
+// MUI Tab
+await page.getByRole('tab', { name: 'Details' }).click();
+
+// MUI Table
+const rows = page.getByRole('row');
+await expect(rows).toHaveCount(expectedCount);
+```
+
+## Repro Test Template
+
+Use this skeleton for reproduction tests:
+
+```typescript
+import { test, expect, Page, BrowserContext } from '@playwright/test';
+
+test.use({
+ video: { mode: 'on', size: { width: 1280, height: 720 } },
+});
+
+test.describe('Repro: — ', () => {
+ let page: Page;
+ let context: BrowserContext;
+
+ test.beforeAll(async ({ browser }) => {
+ context = await browser.newContext();
+ page = await context.newPage();
+ // Navigate to the relevant page
+ });
+
+ test.afterAll(async () => {
+ await context.close();
+ });
+
+ test('', async () => {
+ // Steps to reproduce from Jira
+ // ...
+ // Assertion that should pass when the bug is fixed
+ });
+});
+```
+
+## Running a Single Test
+
+```bash
+# Legacy mode, en locale only (fastest for reproduction)
+APP_MODE=legacy npx playwright test e2e-tests/_repro-.test.ts --project=en
+
+# NFS mode (for verification after fix)
+APP_MODE=nfs npx playwright test e2e-tests/_repro-.test.ts --project=en
+
+# With headed browser (for debugging)
+APP_MODE=legacy npx playwright test e2e-tests/_repro-.test.ts --project=en --headed
+
+# With trace (for detailed debugging)
+APP_MODE=legacy npx playwright test e2e-tests/_repro-.test.ts --project=en --trace on
+```
+
+## Common yarn Scripts
+
+All workspaces follow the same script naming:
+
+```bash
+yarn test:e2e:legacy # APP_MODE=legacy playwright test
+yarn test:e2e:nfs # APP_MODE=nfs playwright test
+yarn test:e2e:all # Run both modes
+yarn tsc:full # Full TypeScript type check
+yarn test --watchAll=false # Unit tests (no watch mode)
+```
diff --git a/skills/bug-fix/references/video-recording.md b/skills/bug-fix/references/video-recording.md
new file mode 100644
index 0000000..6f29d48
--- /dev/null
+++ b/skills/bug-fix/references/video-recording.md
@@ -0,0 +1,148 @@
+# Video Recording: Playwright Capture & Conversion
+
+How to capture before/after screen recordings for bug fix PRs. Reference this in Steps 3, 4, 6, and 7.
+
+## Playwright Video Configuration
+
+The reproduction test must **always create its own browser context** with `recordVideo` to guarantee video capture regardless of how the workspace's e2e infrastructure manages contexts:
+
+```typescript
+test('repro', async ({ browser }) => {
+ const context = await browser.newContext({
+ recordVideo: { dir: 'test-results/', size: { width: 1280, height: 720 } },
+ });
+ const page = await context.newPage();
+
+ // ... test steps ...
+
+ await context.close(); // finalizes the video file
+});
+```
+
+- **`recordVideo.dir`** — directory where Playwright saves the `.webm` file.
+- **`size`** — 1280x720 gives good quality at reasonable file size. Matches most laptop viewports.
+- **`context.close()`** — MUST be called to finalize the video. Without it the file may be incomplete.
+
+All rhdh-plugins workspaces use `@playwright/test` >= 1.60.0, which supports this config.
+
+### Why not `test.use({ video: ... })`?
+
+`test.use()` only applies to Playwright's auto-created contexts. Many rhdh-plugins workspaces (e.g., `lightspeed`) manually create contexts in `beforeAll` helpers, which bypasses `test.use()` entirely. By always creating our own context with `recordVideo`, we avoid this pitfall.
+
+## Where Videos Land
+
+Playwright saves videos to the `test-results/` directory inside the workspace:
+
+```
+workspaces//test-results/
+└── --/
+ └── video.webm
+```
+
+The exact path depends on the test title. After running, find the video:
+
+```bash
+find test-results -name "video.webm" -type f
+```
+
+## Capturing Before/After Videos
+
+### Before fix (Step 4)
+
+After the reproduction test **fails** (bug is present):
+
+```bash
+mkdir -p e2e-tests/_repro-artifacts
+cp test-results/*/video.webm e2e-tests/_repro-artifacts/before-fix.webm
+```
+
+### After fix (Step 6)
+
+Clean the test results first, then re-run:
+
+```bash
+rm -rf test-results/
+APP_MODE=legacy npx playwright test e2e-tests/_repro-.test.ts --project=en
+cp test-results/*/video.webm e2e-tests/_repro-artifacts/after-fix.webm
+```
+
+## Converting to GIF
+
+GitHub PR descriptions support inline images (PNG, GIF, JPEG) but NOT inline `.webm` video. Convert to GIF for embedding.
+
+### With ffmpeg (recommended)
+
+```bash
+ffmpeg -i e2e-tests/_repro-artifacts/before-fix.webm \
+ -vf "fps=10,scale=800:-1" -loop 0 \
+ e2e-tests/_repro-artifacts/before-fix.gif
+
+ffmpeg -i e2e-tests/_repro-artifacts/after-fix.webm \
+ -vf "fps=10,scale=800:-1" -loop 0 \
+ e2e-tests/_repro-artifacts/after-fix.gif
+```
+
+Options explained:
+- `fps=10` — 10 frames per second (balances smoothness vs file size)
+- `scale=800:-1` — scale width to 800px, maintain aspect ratio
+- `-loop 0` — loop the GIF infinitely
+
+### Check if ffmpeg is available
+
+```bash
+which ffmpeg >/dev/null 2>&1 && echo "available" || echo "not found"
+```
+
+### Without ffmpeg (fallback)
+
+If `ffmpeg` is not installed:
+
+1. Keep the `.webm` files as-is.
+2. After the PR is created, upload the `.webm` files as PR comment attachments.
+3. Reference them in the PR body as download links rather than inline images.
+4. Inform the user: "Install `ffmpeg` for inline GIF previews in PRs: `brew install ffmpeg` (macOS) or `sudo apt install ffmpeg` (Linux)."
+
+## Embedding in PR Description
+
+### Automated approach (preferred) — Upload to branch via GitHub Contents API
+
+After `git push`, upload GIFs to `/.github/screenshots/-fix.gif` on the branch using the GitHub Contents API. This is handled automatically by `raise-pr` Step 10.2.
+
+```bash
+TOKEN=$(gh auth token)
+GIF_B64=$(base64 -i e2e-tests/_repro-artifacts/before-fix.gif)
+curl -s -X PUT \
+ -H "Authorization: token $TOKEN" \
+ -H "Accept: application/vnd.github+json" \
+ -d '{"message":"docs: add before-fix recording","content":"'"$GIF_B64"'","branch":""}' \
+ "https://api.github.com/repos///contents//.github/screenshots/before-fix.gif"
+```
+
+Extract the `download_url` from the JSON response — this is the `raw.githubusercontent.com` URL. Use it in the PR body:
+
+```markdown
+## UI before changes
+
+
+## UI after changes
+
+```
+
+The GIF files live on the feature branch only — they never reach `main`. When the PR merges and the branch is deleted, the files disappear. GitHub's camo proxy caches the rendered image permanently in the PR history.
+
+### Fallback — Manual upload
+
+If the Contents API upload fails (permissions error, file too large):
+
+1. Create the PR with placeholder text for the image sections.
+2. Inform the user to manually drag the GIF files into the PR description on GitHub's web UI.
+3. GitHub uploads them to `user-images.githubusercontent.com` and generates permanent URLs.
+
+## Cleanup
+
+After the PR is created and images are uploaded, remove all temporary artifacts:
+
+```bash
+rm -rf e2e-tests/_repro-artifacts/
+rm -rf test-results/
+```
diff --git a/skills/bug-fix/references/workspace-map.md b/skills/bug-fix/references/workspace-map.md
new file mode 100644
index 0000000..c9cb18f
--- /dev/null
+++ b/skills/bug-fix/references/workspace-map.md
@@ -0,0 +1,81 @@
+# Workspace Map: Jira Components to rhdh-plugins Workspaces
+
+Maps Jira issue **Component** fields to workspace directories in `rhdh-plugins`. Used in Step 2 to identify which workspace a bug belongs to.
+
+## Component-to-Workspace Mapping
+
+| Jira Component | Workspace Directory | Has E2E Tests | E2E Modes |
+|----------------|-------------------|---------------|-----------|
+| Adoption Insights | `adoption-insights` | Yes | legacy + nfs |
+| Bulk Import | `bulk-import` | Yes | legacy + nfs |
+| Extensions | `extensions` | Yes | legacy + nfs |
+| Homepage | `homepage` | Yes | legacy + nfs |
+| Lightspeed | `lightspeed` | Yes | legacy + nfs |
+| Global Header | `global-header` | Yes | legacy only |
+| Quickstart | `quickstart` | Yes | legacy + nfs |
+| Scorecard | `scorecard` | Yes | legacy + nfs |
+| Localization | `translations` | Yes | unknown |
+| DCM | `dcm` | Config only | n/a |
+| Orchestrator | `orchestrator` | No | n/a |
+| MCP | `mcp-integrations` | No | n/a |
+
+### Workspaces without Jira component mapping
+
+These workspaces exist in `rhdh-plugins` but may not have a direct Jira Component match. If the Jira issue's Component doesn't match the table above, ask the user which workspace to target.
+
+- `ai-integrations`, `app-defaults`, `augment`, `boost`, `cost-management`
+- `install-dynamic-plugins`, `konflux`, `noop`, `repo-tools`, `theme`, `x2a`
+
+## Runtime Discovery
+
+After identifying the workspace directory, discover its e2e infrastructure dynamically. Do not hardcode workspace-specific details in this file.
+
+### Step 1: Read Playwright config
+
+```
+workspaces//playwright.config.ts
+```
+
+Extract:
+- `LOCALES` array (typically `['en', 'de', 'es', 'fr', 'it', 'ja']`)
+- `FRONTEND_PORT_BASE` and `BACKEND_PORT_BASE`
+- `APP_MODE` support (look for `process.env.APP_MODE`)
+- `startCommand` (legacy vs nfs)
+- `webServer` configuration
+
+### Step 2: Scan e2e helpers
+
+```
+workspaces//e2e-tests/utils/
+```
+
+Common files across workspaces:
+- `translations.ts` — `getTranslations()` helper for i18n-safe selectors
+- `insightsHelpers.ts` / `testHelper.ts` / `helpers.ts` — workspace-specific navigation and interaction helpers
+- `accessibility.ts` — accessibility testing utilities
+- `localeSkip.ts` — locale-specific test skipping logic
+- `events.ts` / `apiUtils.ts` — API mocking and data seeding
+
+### Step 3: Read translation keys
+
+```
+workspaces//plugins/*/src/translations/ref.ts
+```
+
+Contains the full translation key structure. Use these keys with `getTranslations()` to build i18n-safe Playwright selectors like `page.getByText(translations.header.dateRange.defaultLabel)`.
+
+### Step 4: Map components to source files
+
+```
+workspaces//plugins/*/src/components/
+```
+
+Scan the component directory tree to understand which React components render which UI elements. This mapping is used during diagnosis (Step 5) to trace from a failing Playwright selector back to the source code.
+
+## Fuzzy Matching
+
+Jira Component names don't always match workspace directory names exactly. Apply these rules:
+
+1. Lowercase and hyphenate the component name: "Adoption Insights" → `adoption-insights`
+2. Check for known aliases (e.g., "Localization" → `translations`)
+3. If no match, list all workspace directories and ask the user to pick one
diff --git a/skills/raise-pr/SKILL.md b/skills/raise-pr/SKILL.md
new file mode 100644
index 0000000..4d2f309
--- /dev/null
+++ b/skills/raise-pr/SKILL.md
@@ -0,0 +1,351 @@
+---
+name: raise-pr
+description: >
+ Automate the full PR workflow for rhdh-plugins and community-plugins monorepos:
+ detect workspace, build, generate changeset, commit, push, and create the GitHub PR.
+ Auto-detects which repo you're in. Supports --a auto-approve mode to skip all
+ approval gates. Accepts an optional Jira key or URL to link the PR to a Jira issue
+ (adds a PR comment on the Jira ticket). Use when asked to "raise a PR",
+ "create a PR", "submit a PR", "open a PR", "push my changes", "make a PR for this
+ plugin", or "PR workflow". Also use when user says "raise pr", "/pr", or mentions
+ creating a pull request in rhdh-plugins or community-plugins.
+---
+
+
+
+
+As the very first action when the skill is invoked, echo a skill entry banner to the terminal:
+```
+echo "================ Using Raise PR Skill ==========="
+```
+This must happen before any other work (reading references, detecting repo profile, etc.).
+
+
+
+Pre-build cleanup uses `rm -rf plugins/*/dist packages/*/dist` scoped to the workspace directory. If permission errors occur (root-owned files from a prior Docker build), **skip only `yarn build:all`** and warn the user to run `sudo chown -R $(whoami) .` to fix ownership permanently. Never use `sudo` in the skill itself. Never use `find -name dist` or any broad recursive search — that deletes `dist/` inside `node_modules` and breaks everything.
+
+
+
+Only plugins under `plugins/*` with published-source changes need changesets. Always ignore `packages/*` — those are private app/backend packages that are never published. Within each plugin, only include it if changes touch `src/` or other published paths (root `index.ts`, `config.d.ts`, `package.json`). Changes only in `dev/`, `tests/`, `__fixtures__/`, or storybook stories do not require a changeset.
+
+
+
+Capture `git status --porcelain` before builds as the baseline. After builds, only stage files that are new relative to that baseline. Pre-existing dirty files (local config overrides, dev fixtures) must never be staged.
+
+
+
+Before executing each numbered Step, echo a clearly visible banner to the terminal so the user can track progress:
+```
+echo "================ Step N — ==========="
+```
+
+
+
+NEVER bypass this skill by running `gh pr create` or `git push` directly. When another skill (e.g., `bug-fix`) chains into `raise-pr`, you MUST read and follow every step of this skill sequentially — especially Step 10 (push, upload recordings, create PR). Skipping steps or substituting manual commands causes broken PR descriptions, missing recordings, and missing changesets. If you find yourself about to run `gh pr create` without having completed Steps 1–9 first, STOP — you are violating the skill contract.
+
+
+
+When `recordings` caller context is provided, the GIF upload in Step 10.2 is MANDATORY — not optional. You MUST upload both GIF files via the GitHub Contents API and extract real `raw.githubusercontent.com` URLs BEFORE constructing the PR body. NEVER use placeholder URLs, `github.com/user-attachments/assets/` URLs, or any fabricated URLs. If the upload fails, you MUST either retry or inform the user that manual upload is needed — do NOT silently proceed with broken image links.
+
+
+
+
+## Prerequisites
+
+- **`gh` CLI** — GitHub CLI must be installed and authenticated (`gh auth status` should show logged in). Install: https://cli.github.com/
+- **Jira MCP** — The Atlassian Rovo MCP server must be configured in Cursor for Jira comment updates. Setup guide: https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/
+ - If not configured, the skill will skip Jira updates and log a warning.
+- Working checkout of `rhdh-plugins` (or `community-plugins`)
+- `yarn` available on PATH
+
+---
+
+## Mode: check for `--a` flag
+
+Check if the user invoked this skill with `--a` (auto-approve mode).
+
+- **`--a` present:** All approval gates (Steps 4, 8, and 9) are skipped — proceed automatically.
+- **`--a` absent (default):** Approval gates require user confirmation before proceeding.
+
+---
+
+## Step 1 — Detect repo profile
+
+Read `references/repo-profiles.md` and follow the detection logic. Run `git remote -v`, match the remote URLs, and load the matching profile (upstream repo, npm scope, PR body template, changeset docs link).
+
+If no profile matches, ask the user which repo they are targeting before proceeding.
+
+Store the profile values for use in Steps 5, 6, and 10.
+
+---
+
+## Step 1.5 — Resolve Jira context
+
+Read `references/jira-input.md` for parsing rules and REST API patterns.
+
+Resolve a Jira issue from one of these sources (in priority order):
+
+1. **Caller context** — another skill (e.g., `bug-fix`) passes `jira_key`, `jira_url`, and `jira_summary` directly. Skip detection.
+2. **Argument** — the user passed a Jira key or URL as an argument (e.g., `raise-pr RHDHBUGS-1934` or `raise-pr https://redhat.atlassian.net/browse/RHIDP-15252`).
+3. **Branch name** — if the current branch contains a Jira key (e.g., `fix/RHDHBUGS-1934-keyboard-nav`), extract it.
+4. **Prompt** — ask: "Jira issue key or URL? (enter to skip)".
+
+**Parsing rules** (from `references/jira-input.md`):
+
+- If input matches `(RHIDP|RHDHBUGS|RHDHPLAN|RHDHSUPP)-\d+` anywhere in the string, extract that as the key.
+- If input contains `atlassian.net/browse/`, extract everything after `/browse/` as the key.
+- Construct: `jira_url = https://redhat.atlassian.net/browse/`
+
+**Fetch issue summary** (if key resolved and summary not provided by caller):
+
+Use the Jira REST API per `references/jira-input.md` to fetch the issue summary. If auth is not configured or the fetch fails, store `jira_summary = null` and continue — the key and URL are still usable.
+
+Store: `jira_key`, `jira_url`, `jira_summary` (all nullable). If no Jira reference was resolved, all three are null and Jira-specific behavior in later steps is skipped.
+
+---
+
+## Step 2 — Detect workspace(s) from staged changes
+
+1. Run `git diff --cached --name-only` to list all staged files.
+2. If no files are staged, stop: "No staged changes found. Stage your changes with `git add` before running this command."
+3. Extract workspace names from staged file paths. The workspace is the second path segment (e.g., `workspaces/bulk-import/plugins/foo/src/index.ts` → workspace `bulk-import`, path `workspaces/bulk-import`).
+4. If staged files span **multiple** workspaces, inform the user: "Changes detected in **N** workspace(s): ``. Proceed? (Yes/No)". Wait for confirmation. If declined, stop.
+5. Store the workspace names and paths for later steps.
+
+---
+
+## Step 3 — Capture baseline snapshot
+
+Run `git status --porcelain` and save the full output as the **baseline snapshot**. This captures all files that were already dirty or untracked before any build commands run. Used in Step 7 to filter out pre-existing changes.
+
+---
+
+## Step 4 — Create branch (only if on `main`) [APPROVAL GATE]
+
+1. Run `git branch --show-current`.
+2. **If on `main`:**
+ a. Analyze the staged diff (`git diff --cached`) to understand the changes.
+ b. Generate a branch name:
+ - **If `jira_key` is set:** `fix/--` (e.g., `fix/adoption-insights-RHDHBUGS-1934-keyboard-nav-dropdown`). This matches the existing repo convention and enables auto-linking in the Jira Development panel.
+ - **If no Jira key:** `feat/-` (use `fix/` for bug fixes). For multiple workspaces, use a general description.
+ c. **If NOT auto-approve:** present the proposed branch name and a one-line summary. Wait for approval. **If auto-approve:** proceed immediately.
+ d. Run `git checkout -b `.
+3. **If NOT on `main`:** skip branch creation. Inform the user: "Already on branch ``, skipping branch creation."
+
+---
+
+## Step 5 — Build and validate each workspace
+
+For **each** workspace from Step 2, run the following commands **sequentially** inside the workspace directory (e.g., `workspaces/bulk-import`). If any command fails, **stop immediately** and report the error with the failing command, workspace, and output.
+
+### 5.0 — Pre-build cleanup
+
+Remove stale `dist/` directories that may contain root-owned files from previous Docker or sudo builds:
+
+```
+rm -rf plugins/*/dist packages/*/dist
+```
+
+**If `EACCES` permission error occurs:** Set a flag `SKIP_BUILD_ALL=true`. Log a warning with the permanent fix:
+```
+echo "⚠️ Skipping yarn build:all — dist/ has root-owned files. Run 'sudo chown -R $(whoami) .' to fix ownership, then re-run."
+```
+
+Never use `sudo` in the skill itself — just tell the user how to fix it.
+
+### 5.1–5.6 — Build pipeline
+
+Run in order:
+
+1. `yarn` — install dependencies
+2. `yarn prettier:fix` — format code
+3. `yarn tsc:full` — full TypeScript type check
+4. `yarn build:all` — build all packages. **Skip this step if `SKIP_BUILD_ALL=true`** (from 5.0 fallback).
+5. `yarn test --watchAll=false` — run tests (disable Jest watch mode)
+6. `yarn build:api-reports:only` — generate/update API reports (depends on `tsc:full`, always runs)
+
+---
+
+## Step 6 — Generate changeset per workspace
+
+For **each** workspace from Step 2, generate a changeset programmatically. Use the npm scope from the detected repo profile (Step 1).
+
+1. From the staged diff (`git diff --cached`), determine:
+ - Which **plugins** under this workspace are affected (look at `plugins/*` only — **ignore `packages/*`**).
+ - Within each plugin, only include it if changes touch published paths (`src/`, root `index.ts`, `config.d.ts`, `package.json`). Skip plugins with changes only in `dev/`, `tests/`, `__fixtures__/`, or stories.
+ - Read each affected plugin's `package.json` for its npm package name.
+ - Infer the semver bump: `patch` for fixes, `minor` for features, `major` for breaking changes.
+ - **If no plugins have published-source changes, skip changeset generation for this workspace.**
+2. Generate a short summary (1-2 sentences).
+3. Generate a random changeset ID: `--` pattern, lowercase, 5-8 chars per word. Each workspace gets a unique ID.
+4. Write the changeset file to `/.changeset/.md`:
+
+```
+---
+'':
+---
+
+
+```
+
+If multiple packages are affected, list each on its own YAML line.
+
+**Do NOT run `yarn changeset` interactively.** Create files programmatically.
+
+---
+
+## Step 7 — Identify build-generated files
+
+1. Run `git status --porcelain` for the **current snapshot**.
+2. Compare against the **baseline snapshot** from Step 3.
+3. Files only in the current snapshot are build-generated (created by Step 5 builds or Step 6 changesets).
+4. Files already in the baseline are pre-existing — exclude them from staging.
+
+---
+
+## Step 8 — Stage build-generated files [APPROVAL GATE]
+
+1. Present the filtered list of build-generated files from Step 7.
+2. **If NOT auto-approve:** ask the user for approval before staging. **If auto-approve:** stage all automatically.
+3. Run `git add` for each approved file.
+
+---
+
+## Step 9 — Commit [APPROVAL GATE]
+
+1. Run `git diff --cached --stat` to review all staged changes.
+2. Generate a commit message in conventional commit format: `(): ` (e.g., `feat(bulk-import): add batch repository import support`).
+3. **If `jira_url` is set**, append a `Fixes:` trailer in the commit body:
+
+```
+fix(adoption-insights): enable keyboard navigation in header date-range dropdown
+
+Fixes: https://redhat.atlassian.net/browse/RHDHBUGS-1934
+```
+
+4. **If NOT auto-approve:** present the commit message and staged file summary. Wait for approval. **If auto-approve:** commit immediately.
+5. Commit with the **`-s` flag** (Signed-off-by):
+
+```
+git commit -s -m "" -m "Fixes: "
+```
+
+If no `jira_url`, omit the second `-m` flag.
+
+---
+
+## Step 10 — Push and create PR
+
+1. Push the branch:
+
+```
+git push -u origin HEAD
+```
+
+2. **Upload recording GIFs to branch [HARD GATE when `recordings` is provided]**:
+
+ Skip this sub-step ONLY if `recordings` caller context is NOT provided. When `recordings` IS provided, this sub-step is MANDATORY — do NOT skip, defer, or substitute with placeholder URLs.
+
+ a. Verify both local GIF files exist before proceeding:
+ ```
+ ls -la
+ ```
+ If either file is missing, STOP and inform the user.
+
+ b. Determine the fork owner and repo name from the `origin` remote URL (e.g., `its-mitesh-kumar/rhdh-plugins`).
+ c. Get the current branch name: `git branch --show-current`.
+ d. For each GIF file (`recordings.before` and `recordings.after`), upload to the branch via the GitHub Contents API:
+
+ ```
+ TOKEN=$(gh auth token)
+ GIF_B64=$(base64 -i )
+ RESPONSE=$(curl -s -X PUT \
+ -H "Authorization: token $TOKEN" \
+ -H "Accept: application/vnd.github+json" \
+ -d '{"message":"docs: add -fix recording","content":"'"$GIF_B64"'","branch":""}' \
+ "https://api.github.com/repos///contents//.github/screenshots/-fix.gif")
+ echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); url=d.get('content',{}).get('download_url',''); print('download_url:', url); exit(0 if url.startswith('https://raw.githubusercontent.com') else 1)"
+ ```
+
+ e. Extract the `download_url` from the JSON response — this is the `raw.githubusercontent.com` URL. **Verify** that the URL starts with `https://raw.githubusercontent.com/` — if it does not, the upload failed.
+ f. Store both URLs: `before_gif_url`, `after_gif_url`.
+ g. Echo a verification banner:
+ ```
+ echo "✅ GIF upload verified: before=$before_gif_url after=$after_gif_url"
+ ```
+
+ **If upload fails:** Retry once. If the retry also fails, log a warning and inform the user to manually drag GIFs into the PR description on GitHub. Use placeholder text `_(Recording upload failed — drag GIF manually)_` in the PR body instead of a broken image link. NEVER fabricate a URL that looks real but does not resolve.
+
+3. Generate a PR title from the commit subject line.
+4. Build the PR body from the detected repo profile template (Step 1). The body has conditional sections:
+ - **`## Fixed`** — include only if `jira_key` is set. Format: `- []() — `.
+ - **`## UI before changes` / `## UI after changes`** — include only if `recordings` are provided in caller context. Use the `raw.githubusercontent.com` URLs from sub-step 2 in the markdown: `![Before fix]()` / `![After fix]()`. **CRITICAL**: Both URLs MUST have been obtained from the GitHub Contents API response in sub-step 2. NEVER construct, guess, or fabricate these URLs. If sub-step 2 failed and you have no valid URLs, use the placeholder text from the failure path instead of a broken image link.
+ - **`pr_description_extra`** — if provided by caller context (e.g., root cause analysis from `bug-fix`), insert it after the generated description paragraph.
+ - **`## Test Plan`** — include only if `test_plan` is provided in caller context. Insert the markdown checklist as-is.
+ - **`## Checklist`** — always present.
+ - **`## Note`** — include only if both `recordings` AND `jira_key` are provided (indicating an automated bug-fix PR). Contains the skill attribution disclaimer.
+5. Create the PR using `gh pr create` with the repo-appropriate template. Use the upstream repo value for `--repo` and `main` for `--base`. Pass the body via HEREDOC:
+
+```
+gh pr create --repo --base main --title "" --body "$(cat <<'EOF'
+
+EOF
+)"
+```
+
+6. Capture and store the PR URL for Step 11.
+7. Display the PR URL.
+
+---
+
+## Step 11 — Post-PR Jira updates
+
+**Skip this step entirely if `jira_key` is null.**
+
+Add a comment on the Jira issue documenting the PR submission:
+
+```
+Use CallMcpTool: server="user-jira", toolName="add_jira_comment"
+Arguments: { "issueKey": "", "body": "PR submitted: " }
+```
+
+**If the MCP call fails:** Log a warning and continue — the PR has been created successfully:
+```
+echo "⚠️ Could not add comment to . Add manually: "
+```
+
+---
+
+## Gotchas
+
+- **Fork workflows:** The user may have `origin` pointing to their fork and `upstream` to the canonical repo. Detection checks all remotes, not just `origin`. The `--repo` flag on `gh pr create` targets the canonical upstream regardless of which remote `origin` points to.
+- **Multiple workspaces:** Each workspace gets its own build cycle (Step 5) and changeset (Step 6). The commit (Step 9) bundles everything into one commit. If the user prefers separate PRs per workspace, they should stage and run the skill once per workspace.
+- **Changeset ID collisions:** Each workspace must get a unique random ID. If generating for multiple workspaces in one run, track used IDs and avoid duplicates.
+
+## Caller Context (optional)
+
+When another skill chains into `raise-pr`, it may provide a **caller context** with pre-resolved values. If present, these override the defaults — skip detection for any field that is already provided.
+
+| Field | Type | Used in | Description |
+|-------|------|---------|-------------|
+| `jira_key` | string | Steps 1.5, 4, 9, 10, 11 | Pre-resolved Jira issue key (skip Step 1.5 detection) |
+| `jira_url` | string | Steps 9, 10, 11 | Full Jira browse URL |
+| `jira_summary` | string | Step 10 | Issue summary for the PR body `## Fixed` section |
+| `recordings` | object | Step 10 | `{ before: "", after: "" }` — local GIF paths; `raise-pr` uploads them to the branch via GitHub Contents API and uses the resulting `raw.githubusercontent.com` URLs in the PR body |
+| `pr_description_extra` | string | Step 10 | Extra text inserted after the description (e.g., root cause analysis) |
+| `test_plan` | string | Step 10 | Markdown checklist for the `## Test Plan` section (e.g., `"- [ ] Open menu\n- [ ] Verify scrollbar on hover"`) |
+
+Skills that chain into `raise-pr`:
+
+- **`bug-fix`** — provides all six fields after reproducing and fixing a Jira bug with Playwright video recordings.
+
+
+
+## Reference Index
+
+| Reference | Load when... |
+|-----------|-------------|
+| `references/repo-profiles.md` | Always — at the start of every invocation (Step 1) |
+| `references/jira-input.md` | When resolving Jira context (Step 1.5) |
+
+
diff --git a/skills/raise-pr/references/jira-input.md b/skills/raise-pr/references/jira-input.md
new file mode 100644
index 0000000..ddad896
--- /dev/null
+++ b/skills/raise-pr/references/jira-input.md
@@ -0,0 +1,129 @@
+# Jira Input Parsing & REST API Patterns
+
+Shared reference for resolving Jira issue keys from user input and performing Jira REST API operations. Used by `raise-pr` (Step 1.5, Step 11) and `bug-fix` (Step 1).
+
+## Parsing Jira References
+
+Accept any of these formats and normalize to a key + URL pair:
+
+| Input format | Example | Extraction |
+|-------------|---------|------------|
+| Bare key | `RHDHBUGS-1934` | Match directly |
+| Browse URL | `https://redhat.atlassian.net/browse/RHDHBUGS-1934` | Extract after `/browse/` |
+| URL without scheme | `redhat.atlassian.net/browse/RHIDP-15252` | Extract after `/browse/` |
+| URL with query params | `https://redhat.atlassian.net/browse/RHIDP-15252?focusedId=123` | Extract key between `/browse/` and `?` |
+
+### Extraction rules
+
+1. If the input contains `atlassian.net/browse/`, extract the path segment immediately after `/browse/` (strip any query string or fragment).
+2. Otherwise, scan the full input string for a match against the pattern `(RHIDP|RHDHBUGS|RHDHPLAN|RHDHSUPP)-\d+`.
+3. If neither matches, the input is invalid — ask the user to provide a valid key or URL.
+
+### Normalization
+
+Once the key is extracted:
+
+```
+jira_key = "RHDHBUGS-1934"
+jira_url = "https://redhat.atlassian.net/browse/RHDHBUGS-1934"
+```
+
+Always construct `jira_url` from the key — do not store the user's raw URL (it may have query params or fragments).
+
+## Authentication
+
+All REST API calls use the `.jira-token` file. Locate it next to the `acli` binary:
+
+```bash
+ACLI_PATH="$(readlink -f "$(which acli)" 2>/dev/null || which acli)"
+TOKEN_FILE="$(dirname "$ACLI_PATH")/.jira-token"
+AUTH="$(cat "$TOKEN_FILE")"
+```
+
+The file contains `email:api_token` in a single line. **Never read the token into the conversation context** — only use it in shell commands via variable substitution.
+
+If `acli` is not on PATH or `.jira-token` does not exist, warn the user: "Jira REST API auth not configured. Run `rhdh-jira setup` or see rhdh-jira skill for setup instructions." Continue without Jira operations.
+
+## REST API Patterns
+
+Base URL: `https://redhat.atlassian.net`
+
+### Fetch issue summary
+
+```bash
+curl -s -u "$AUTH" \
+ "https://redhat.atlassian.net/rest/api/3/issue/$JIRA_KEY?fields=summary,status" | \
+ python3 -c "import json,sys; d=json.load(sys.stdin); print(d['fields']['summary'])"
+```
+
+### Add Web Link (remote link)
+
+```bash
+curl -s -X POST \
+ -u "$AUTH" \
+ -H "Content-Type: application/json" \
+ -d "{\"object\": {\"url\": \"$PR_URL\", \"title\": \"$PR_TITLE\"}}" \
+ "https://redhat.atlassian.net/rest/api/3/issue/$JIRA_KEY/remotelink"
+```
+
+Returns `201 Created` on success with `{"id": ..., "self": "..."}`.
+
+### Add comment
+
+Prefer `acli` when available:
+
+```bash
+acli jira workitem comment add --key "$JIRA_KEY" --comment "PR submitted: $PR_URL" --yes
+```
+
+REST API fallback (requires ADF format):
+
+```bash
+curl -s -X POST \
+ -u "$AUTH" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "body": {
+ "type": "doc",
+ "version": 1,
+ "content": [{"type": "paragraph", "content": [{"type": "text", "text": "PR submitted: '"$PR_URL"'"}]}]
+ }
+ }' \
+ "https://redhat.atlassian.net/rest/api/3/issue/$JIRA_KEY/comment"
+```
+
+### Query available transitions
+
+```bash
+curl -s -u "$AUTH" \
+ "https://redhat.atlassian.net/rest/api/3/issue/$JIRA_KEY/transitions" | \
+ python3 -c "
+import json, sys
+data = json.load(sys.stdin)
+for t in data.get('transitions', []):
+ print(f\"{t['id']:>5} {t['to']['name']}\")
+"
+```
+
+### Execute transition
+
+```bash
+curl -s -X POST \
+ -u "$AUTH" \
+ -H "Content-Type: application/json" \
+ -d "{\"transition\": {\"id\": \"$TRANSITION_ID\"}}" \
+ "https://redhat.atlassian.net/rest/api/3/issue/$JIRA_KEY/transitions"
+```
+
+Returns `204 No Content` on success.
+
+## Error Handling
+
+| HTTP Status | Meaning | Action |
+|-------------|---------|--------|
+| 200/201/204 | Success | Continue |
+| 400 | Bad request | Check payload format. For transitions, the requested transition may not be valid from the current status. |
+| 401 | Unauthorized | `.jira-token` is missing, malformed, or expired. Warn user. |
+| 403 | Forbidden | User lacks permission on this issue. Warn user. |
+| 404 | Not found | Issue key is wrong or issue does not exist. Warn user. |
+| 429 | Rate limited | Wait 5 seconds, retry once. |
diff --git a/skills/raise-pr/references/repo-profiles.md b/skills/raise-pr/references/repo-profiles.md
new file mode 100644
index 0000000..f2e144b
--- /dev/null
+++ b/skills/raise-pr/references/repo-profiles.md
@@ -0,0 +1,91 @@
+# Repo Profiles
+
+Auto-detect which repository you are in and load the matching profile. Read this file at the start of every `raise-pr` invocation.
+
+## Detection
+
+Run `git remote -v` and inspect all remote URLs (fetch lines). Match against the patterns below. If multiple remotes exist (e.g. `origin` pointing to a fork and `upstream` pointing to the canonical repo), prefer the canonical match.
+
+| URL pattern | Profile |
+|-------------|---------|
+| Contains `rhdh-plugins` (but NOT `community-plugins`) | **rhdh-plugins** |
+| Contains `community-plugins` | **community-plugins** |
+| Neither matches | Ask the user: "Which repo are you targeting? (1) rhdh-plugins (2) community-plugins" |
+
+## Profile: rhdh-plugins
+
+| Field | Value |
+|-------|-------|
+| Upstream repo | `redhat-developer/rhdh-plugins` |
+| npm scope | `@red-hat-developer-hub` |
+| Changeset `fixed` group | `["@red-hat-developer-hub/*"]` |
+| Changeset docs link | `https://github.com/redhat-developer/rhdh-plugins/blob/main/CONTRIBUTING.md#creating-changesets` |
+| PR base branch | `main` |
+| Commit signing | `-s` (Signed-off-by) |
+
+### PR body template (rhdh-plugins)
+
+The template has conditional sections. Include or omit them based on the resolved Jira context and caller context from Step 1.5.
+
+```
+## Description
+
+
+
+
+## Fixed ← include only if jira_key is set
+- []() —
+
+## UI before changes ← include only if recordings provided by caller
+![Before fix]() ← raw.githubusercontent.com URL from Step 10.2
+
+## UI after changes ← include only if recordings provided by caller
+![After fix]() ← raw.githubusercontent.com URL from Step 10.2
+
+## Test Plan ← include only if test_plan provided by caller
+
+
+## Checklist
+- [x] A changeset describing the change and affected packages. ([more info](https://github.com/redhat-developer/rhdh-plugins/blob/main/CONTRIBUTING.md#creating-changesets))
+- [ ] Added or Updated documentation
+- [ ] Tests for new functionality and regression tests for bug fixes
+- [ ] Screenshots attached (for UI changes)
+
+## Note ← include only if recordings AND jira_key are both provided
+> This bug fix was identified and implemented using the [bug-fix](https://github.com/redhat-developer/rhdh-plugins/blob/main/.agents/skills/bug-fix/SKILL.md) and [raise-pr](https://github.com/redhat-developer/rhdh-plugins/blob/main/.agents/skills/raise-pr/SKILL.md) agent skills. Please verify the fix thoroughly before merging.
+```
+
+**When no Jira key is set**: omit `## Fixed` entirely.
+**When no recordings provided**: omit both `## UI before changes` and `## UI after changes`.
+**When no test_plan provided**: omit `## Test Plan` entirely.
+**When recordings AND jira_key are NOT both present**: omit `## Note` entirely.
+**When all optional sections are absent**: the template reduces to `## Description` + `## Checklist` (the minimal form).
+
+**Note on image URLs**: The `` and `` placeholders are replaced with real `raw.githubusercontent.com` URLs after uploading the GIF files to the branch via GitHub Contents API (Step 10.2 in the main skill). These are NOT local file paths.
+
+## Profile: community-plugins
+
+| Field | Value |
+|-------|-------|
+| Upstream repo | `backstage/community-plugins` |
+| npm scope | `@backstage-community` |
+| Changeset `fixed` group | `[]` (no fixed versioning) |
+| Changeset docs link | `https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#creating-changesets` |
+| PR base branch | `main` |
+| Commit signing | `-s` (Signed-off-by — DCO required) |
+
+### PR body template (community-plugins)
+
+```
+## Hey, I just made a Pull Request!
+
+
+
+#### Checklist
+
+- [x] A changeset describing the change and affected packages. ([more info](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#creating-changesets))
+- [ ] Added or updated documentation
+- [ ] Tests for new functionality and regression tests for bug fixes
+- [ ] Screenshots attached (for UI changes)
+- [x] All your commits have a `Signed-off-by` line in the message. ([more info](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#developer-certificate-of-origin))
+```
diff --git a/uv.lock b/uv.lock
index 8d77fb0..86223f5 100644
--- a/uv.lock
+++ b/uv.lock
@@ -195,7 +195,7 @@ wheels = [
[[package]]
name = "rhdh-skill"
-version = "0.5.0"
+version = "0.7.0"
source = { virtual = "." }
[package.optional-dependencies]