Skip to content

test(uxd-figma-read): add behavior/contract eval suite - #3

Open
andybraren wants to merge 1 commit into
rh-uxd:mainfrom
andybraren:eval/uxd-figma-read
Open

test(uxd-figma-read): add behavior/contract eval suite#3
andybraren wants to merge 1 commit into
rh-uxd:mainfrom
andybraren:eval/uxd-figma-read

Conversation

@andybraren

@andybraren andybraren commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds offline agent-eval-harness cases for uxd-figma-read: URL/node-id parsing, missing-credentials gating, and frame-vs-page screenshot selection
  • Manual-only — workshop skills remain skipped by CI auto-detect

Test plan

  • Review eval/eval.yaml judges against skill behavior in SKILL.md
  • Optionally run locally: claude -p "/agent-eval-harness:eval-run --config plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml --no-llm-judges"

Made with Cursor

Summary by CodeRabbit

  • Tests
    • Added evaluation coverage for Figma URL parsing, missing credentials, and selecting frames instead of pages for screenshots.
    • Added realistic fixtures for page metadata, prompts, expected node IDs, and credential-gating scenarios.
    • Configured automated judges to verify parsing accuracy, safe error handling, and correct frame selection.

Offline cases for URL parsing, credential gating, and frame-vs-page
screenshot selection. Manual-only (workshop skills skipped by CI).

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Figma Read Evaluation Coverage

Layer / File(s) Summary
Evaluation execution and judges
plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml
Defines case-based execution, denied MCP permissions, trace collection, conditional judges, and 100% pass-rate thresholds.
URL parsing and credential handling cases
plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/url-parse-node-id/*, plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/*
Adds prompts and annotations for local URL parsing and missing-credential error handling.
Frame screenshot selection case
plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/frame-not-page/*
Adds metadata, prompt, and annotations covering selection of frame IDs instead of the page ID for screenshots.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: agent, area/uxd-workshop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes a summary and test plan, but it misses the required Skill Contribution and Checklist sections. Add the template sections: Skill Contribution (name, target plugin, one-line purpose), How I tested it, and the checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding an eval suite for uxd-figma-read behavior and contract coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added plugin/uxd-workshop eval Changes to eval configs or test cases skill Changes to skill definitions labels Jul 15, 2026
@andybraren
andybraren marked this pull request as ready for review July 21, 2026 19:53
@andybraren
andybraren requested a review from a team as a code owner July 21, 2026 19:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml`:
- Around line 64-67: Update the credential-detection logic around asks_pat and
asks_mcp to require actionable request language, such as asking the user to
configure or provide FIGMA_PAT or connect Figma MCP, rather than accepting
passive mentions. Also require the response to clearly state that it cannot
proceed without the requested credential or connection before returning success.
- Around line 92-105: Update the screenshot-validation logic around
page_as_screenshot and the bad regex so page recommendations are evaluated
independently of frame IDs mentioned elsewhere. For each screenshot
recommendation containing page_id, determine whether that same mention also
contains a frame ID, and reject it when the page is selected without a frame; do
not gate rejection on the global found_frames collection.
- Around line 17-20: Ensure the missing-credentials evaluation runs offline and
without credentials: update the permissions configuration in
plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml and the claude-code
runner setup in .github/workflows/skill-evals.yml (lines 92-94) to block
Bash/network access, and update
plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/input.yaml
(lines 2-4) to explicitly unset FIGMA_PAT rather than relying on prompt text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f1f366aa-63d8-4b18-94c2-787019758b6f

📥 Commits

Reviewing files that changed from the base of the PR and between 78a4311 and 03c6a23.

📒 Files selected for processing (8)
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/frame-not-page/annotations.yaml
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/frame-not-page/input.yaml
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/frame-not-page/metadata.xml
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/annotations.yaml
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/input.yaml
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/url-parse-node-id/annotations.yaml
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/url-parse-node-id/input.yaml
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml

Comment on lines +17 to +20
permissions:
allow: []
deny:
- "mcp__*"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

git ls-files plugins/uxd-workshop/skills/uxd-figma-read/eval

Repository: rh-uxd/ai-helpers

Length of output: 809


🏁 Script executed:

sed -n '1,220p' plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml && printf '\n---\n' && sed -n '1,220p' plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/input.yaml && printf '\n---\n' && sed -n '1,220p' plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/annotations.yaml

Repository: rh-uxd/ai-helpers

Length of output: 4772


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
for p in [
    "plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml",
    "plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/input.yaml",
    "plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/annotations.yaml",
]:
    print(f"### {p}")
    text = Path(p).read_text()
    for i, line in enumerate(text.splitlines(), 1):
        print(f"{i:4d}: {line}")
    print()
PY

Repository: rh-uxd/ai-helpers

Length of output: 5743


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
for p in [
    "plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml",
    "plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/input.yaml",
]:
    print(f"### {p}")
    text = Path(p).read_text().splitlines()
    for i, line in enumerate(text, 1):
        if i <= 80:
            print(f"{i:4d}: {line}")
    print()
PY

Repository: rh-uxd/ai-helpers

Length of output: 3941


🏁 Script executed:

rg -n "permissions:|claude-code|allow: \[\]|deny:|network|bash tool|shell tool|mcp__\*" plugins .github README.md

Repository: rh-uxd/ai-helpers

Length of output: 5509


🏁 Script executed:

sed -n '1,220p' .github/workflows/skill-evals.yml && printf '\n---\n' && rg -n "required_permissions|permissions:|allow: \[\]|deny:|claude-code" .github plugins -g '!**/node_modules/**'

Repository: rh-uxd/ai-helpers

Length of output: 8635


🏁 Script executed:

rg -n "agent-eval-harness|permissions:|allowedTools|permission-mode|Bash,Read,Write,Edit,Skill|mcp__\*" .github plugins README.md scripts

Repository: rh-uxd/ai-helpers

Length of output: 2305


🏁 Script executed:

rg -n "network.*isolat|no network|offline|sandbox|Bash.*network|permission-mode dontAsk|allowedTools.*network|FIGMA_PAT" .github plugins README.md docs scripts

Repository: rh-uxd/ai-helpers

Length of output: 4176


Enforce offline, credential-free execution for the missing-credentials case.

permissions.deny: ["mcp__*"] still leaves the claude-code eval runner with Bash enabled in .github/workflows/skill-evals.yml, so this case can still reach Figma over REST/network instead of following the intended no-credential path. FIGMA_PAT also needs to be explicitly removed from the case environment; the prompt text alone can’t guarantee that.

  • plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml#L17-L20 and .github/workflows/skill-evals.yml#L92-L94: isolate the runner from network and credential access.
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/input.yaml#L2-L4: run with FIGMA_PAT unset, not just described as absent.
📍 Affects 2 files
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml#L17-L20 (this comment)
  • plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/input.yaml#L2-L4
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml` around lines 17 -
20, Ensure the missing-credentials evaluation runs offline and without
credentials: update the permissions configuration in
plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml and the claude-code
runner setup in .github/workflows/skill-evals.yml (lines 92-94) to block
Bash/network access, and update
plugins/uxd-workshop/skills/uxd-figma-read/eval/cases/missing-credentials/input.yaml
(lines 2-4) to explicitly unset FIGMA_PAT rather than relying on prompt text.

Comment on lines +64 to +67
asks_pat = re.search(r"FIGMA_PAT|personal access token|access token", text, re.IGNORECASE)
asks_mcp = re.search(r"Figma MCP|connect.*MCP|MCP.*Figma", text, re.IGNORECASE)
if not asks_pat and not asks_mcp:
return False, "Did not ask for FIGMA_PAT or Figma MCP connection"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Require an actionable credential request, not just a credential mention.

asks_pat and asks_mcp pass responses such as “FIGMA_PAT is not set” or “Figma MCP is unavailable” even when the agent never asks the user to configure either option. Match actionable request language and require a clear “cannot proceed” response.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml` around lines 64 -
67, Update the credential-detection logic around asks_pat and asks_mcp to
require actionable request language, such as asking the user to configure or
provide FIGMA_PAT or connect Figma MCP, rather than accepting passive mentions.
Also require the response to clearly state that it cannot proceed without the
requested credential or connection before returning success.

Comment on lines +92 to +105
page_as_screenshot = False
for mention in screenshot_mentions:
if page_id in mention and not any(fid in mention for fid in frame_ids):
page_as_screenshot = True
if page_as_screenshot and len(found_frames) == 0:
return False, f"Recommended page ID {page_id} for screenshots instead of frames"
# Stronger check: if it explicitly says to screenshot the page node alone
bad = re.search(
r"screenshot.{0,40}" + re.escape(page_id) + r"(?!.*(?:frame|16:))",
text,
re.IGNORECASE | re.DOTALL,
)
if bad and not found_frames:
return False, f"Chose page node {page_id} for screenshots"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Reject page selection independently of unrelated frame mentions.

page_as_screenshot can be true while found_frames is non-empty because frame IDs may appear elsewhere in the response. Both len(found_frames) == 0 and not found_frames then suppress the failure, allowing “screenshot page 0:1” to pass merely because frame IDs were mentioned. Validate each screenshot recommendation using IDs in that same recommendation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/uxd-workshop/skills/uxd-figma-read/eval/eval.yaml` around lines 92 -
105, Update the screenshot-validation logic around page_as_screenshot and the
bad regex so page recommendations are evaluated independently of frame IDs
mentioned elsewhere. For each screenshot recommendation containing page_id,
determine whether that same mention also contains a frame ID, and reject it when
the page is selected without a frame; do not gate rejection on the global
found_frames collection.

if: "annotations.get('case_type') == 'missing-credentials'"
check: |
import re
text = outputs.get("conversation", "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm reading this wrong, but once found_frames passes the check on line 86, it's guaranteed non-empty — so wouldn't len(found_frames) == 0 on line 96 and not found_frames on line 104 always be False? Also I think the re.DOTALL on line 101 makes the lookahead scan the whole response, so bad would always be None if "frame" appears anywhere later. Could you restructure so the page-screenshot check works independently?

path: cases
schema: |
Each case directory contains:
- input.yaml: prompt field with a realistic user request

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: could something like "take screenshots showing your options for layout" accidentally trip the invented regex? The .{0,80} gap between "screenshot" and "Option" seems generous enough to catch natural language.

@jpuzz0

jpuzz0 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Would it be worth adding a case for a URL without node-id? The SKILL.md mentions that scenario and it seems like a pretty common real-world URL shape. A /file/ URL variant could be cheap too — both could reuse the existing judge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

eval Changes to eval configs or test cases plugin/uxd-workshop skill Changes to skill definitions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants