본문으로 건너뛰기
cccskills
무료GitHub에 공개

a11y-audit

Run WCAG 2.2 Level AA accessibility audits against generated UI. Use when a story has ui:true or when asked to audit accessibility.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구GitHub
설치 방법 보기

포함된 파일(1)

  • SKILL.md8.1 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

SKILL: a11y-audit

Purpose

Run WCAG 2.2 Level AA accessibility audits against generated UI. Uses axe-core (via @axe-core/cli) or pa11y as available. Posts findings as PR comments. Blocks merge on AA violations. Required for every story with ui: true.

Target awareness

When design.target (project.config.yaml, default web) is tui, skip the browser tooling (axe/pa11y) and the preview URL. Instead evaluate the terminal a11y checklist from docs/design/design-targets.md and write the findings to docs/design/mockups/<id>.a11y.json using the SAME object shape the web path produces — an object with violations[], where each violation has id, impact (critical|serious|moderate|minor), description, help, and nodes[] ({target:[...], failureSummary}). The gate (scripts/sdlc/a11y-gate.sh) reads only violations[].impact, so matching this shape keeps it unchanged. Map checklist failures to impacts: keyboard-reachable→critical, focus-visible→serious, color-contrast→serious, color-only-signaling→serious, no-color-support→moderate, min-width-resize→moderate. The WCAG criteria, tool-detection, axe/pa11y, and PR-comment sections below apply to web targets.

WCAG 2.2 AA — Minimum Requirements

CriterionLevelWhat to check
1.1.1 Non-text ContentAAll <img> have alt. Icons used as UI controls have labels.
1.3.1 Info and RelationshipsASemantic HTML: headings, lists, tables, landmarks used correctly.
1.4.3 Contrast (minimum)AANormal text ≥ 4.5:1. Large text ≥ 3:1.
1.4.11 Non-text ContrastAAUI components and focus indicators ≥ 3:1 against background.
2.1.1 KeyboardAAll interactive elements reachable and operable via keyboard.
2.4.3 Focus OrderATab order is logical and follows visual layout.
2.4.7 Focus VisibleAAFocus indicator visible on all interactive elements.
3.3.1 Error IdentificationAErrors identified in text, not color alone.
3.3.2 Labels or InstructionsAAll inputs have labels.
4.1.2 Name, Role, ValueAAll UI components have accessible name, role, and state.

Tool Detection

detect_tool() {
  if command -v axe &>/dev/null; then
    echo "axe"
  elif command -v pa11y &>/dev/null; then
    echo "pa11y"
  elif npx --yes @axe-core/cli --version &>/dev/null 2>&1; then
    echo "axe-npx"
  else
    echo "none"
  fi
}
TOOL=$(detect_tool)

Run with axe-core CLI

URL="${1:-http://localhost:3000}"  # preview URL or Storybook story URL
STORY_ID="${2:-unknown}"
REPORT="docs/design/mockups/${STORY_ID}.a11y.json"

mkdir -p docs/design/mockups

axe "$URL" \
  --stdout \
  --tags wcag2a,wcag2aa,wcag21aa,wcag22aa \
  > "$REPORT"

VIOLATIONS=$(python3 -c "import json,sys; d=json.load(open('$REPORT')); print(len(d.get('violations',[])))")
echo "[a11y-audit] axe  $URL  violations=$VIOLATIONS"

Run with pa11y

URL="${1:-http://localhost:3000}"
STORY_ID="${2:-unknown}"
REPORT="docs/design/mockups/${STORY_ID}.a11y.json"

pa11y "$URL" \
  --standard WCAG2AA \
  --reporter json \
  > "$REPORT" 2>&1

ISSUES=$(python3 -c "import json,sys; d=json.load(open('$REPORT')); print(len(d) if isinstance(d,list) else 0)")
echo "[a11y-audit] pa11y  $URL  issues=$ISSUES"

Parse Results and Classify

import json, sys

report_path = sys.argv[1]
data = json.load(open(report_path))

# axe format
violations = data.get("violations", [])
passes     = data.get("passes", [])

critical = [v for v in violations if v["impact"] in ("critical", "serious")]
moderate = [v for v in violations if v["impact"] == "moderate"]
minor    = [v for v in violations if v["impact"] == "minor"]

print(f"CRITICAL/SERIOUS: {len(critical)}")
print(f"MODERATE:         {len(moderate)}")
print(f"MINOR:            {len(minor)}")
print(f"PASSES:           {len(passes)}")

# Merge gate: block on critical + serious
if critical:
    print("\nMERGE BLOCKED — resolve the following before merging:")
    for v in critical:
        print(f"  [{v['impact'].upper()}] {v['id']}: {v['description']}")
        for node in v.get("nodes", [])[:2]:
            print(f"    → {node.get('target', ['?'])[0]}: {node.get('failureSummary','')[:120]}")
    sys.exit(1)

Post Findings as PR Comment

STORY_ID="auth-reset-0001"
PR_NUMBER=$(gh pr list --head "$(git branch --show-current)" --json number --jq '.[0].number')
REPORT="docs/design/mockups/${STORY_ID}.a11y.json"

# Build comment body
COMMENT=$(python3 - <<'EOF'
import json, sys

data = json.load(open(sys.argv[1]))
violations = data.get("violations", [])

if not violations:
    print("## ✅ A11y Audit Passed\n\nNo WCAG 2.2 AA violations found.")
    sys.exit(0)

lines = [f"## ⚠️ A11y Audit: {len(violations)} violation(s) found\n"]
for v in violations[:10]:
    impact = v.get("impact","").upper()
    lines.append(f"### [{impact}] {v['id']}")
    lines.append(f"_{v['description']}_\n")
    for node in v.get("nodes",[])[:2]:
        selector = ', '.join(node.get("target",[]))
        lines.append(f"- `{selector}`")
        lines.append(f"  {node.get('failureSummary','')[:200]}")
    lines.append("")

if len(violations) > 10:
    lines.append(f"_...and {len(violations)-10} more. See full report in `docs/design/mockups/`._")

print('\n'.join(lines))
EOF
"$REPORT")

gh pr comment "$PR_NUMBER" --body "$COMMENT"
echo "[a11y-audit] PR_COMMENT  #$PR_NUMBER  violations=$(echo "$COMMENT" | grep -c '\[CRITICAL\]\|\[SERIOUS\]' || echo 0)"

Merge Gate Check

Called from scripts/sdlc/a11y-gate.sh (added in Phase VII):

STORY_FILE="$1"
UI=$(python3 -c "
import re
m = re.search(r'^ui:\s*(true|false)', open('$STORY_FILE').read(), re.MULTILINE)
print(m.group(1) if m else 'false')
")

if [ "$UI" != "true" ]; then
  echo "[a11y-audit] SKIP  ui:false story — no audit required"
  exit 0
fi

STORY_ID=$(python3 -c "
import re
m = re.search(r'^id:\s*[\"\'](.*?)[\"\']', open('$STORY_FILE').read(), re.MULTILINE)
print(m.group(1) if m else 'unknown')
")
REPORT="docs/design/mockups/${STORY_ID}.a11y.json"

if [ ! -f "$REPORT" ]; then
  echo "[a11y-audit] FAIL  no audit report found for $STORY_ID — run audit before merging"
  exit 1
fi

python3 scripts/sdlc/parse-a11y-report.py "$REPORT"

Manual Audit Checklist (no tool available)

When TOOL=none, perform a structured manual check:

## Manual A11y Checklist — {story_id}

### Perceivable
- [ ] All images have meaningful `alt` text (or `alt=""` for decorative)
- [ ] Color is not the sole means of conveying information
- [ ] Normal text contrast ≥ 4.5:1 (check with browser DevTools)
- [ ] Large text contrast ≥ 3:1

### Operable
- [ ] Tab through the entire form/component with keyboard only
- [ ] All actions reachable without mouse
- [ ] No keyboard trap
- [ ] Focus indicator visible on every interactive element

### Understandable
- [ ] Form inputs have visible labels (not just placeholders)
- [ ] Errors described in text, not color alone
- [ ] Error messages are specific and actionable

### Robust
- [ ] Correct semantic elements: `<button>`, `<input>`, `<label>`
- [ ] ARIA attributes only where native HTML is insufficient
- [ ] Works with browser zoom at 200%

Failure Modes

ConditionAction
No tool availableFall back to manual checklist. Log TOOL=none — manual audit required.
Preview URL not reachableLog URL_UNREACHABLE. Do not skip audit — escalate.
ui: false storySkip audit. Log SKIP — ui:false.
Critical violations foundPost PR comment. Exit non-zero. Block merge.
Report already exists and is currentSkip re-run. Log SKIP — current report exists.

Logging

[a11y-audit] RUN      http://localhost:6006/... story=auth-reset-0001  tool=axe
[a11y-audit] RESULT   violations=0  passes=24  WCAG2AA=PASS
[a11y-audit] RESULT   violations=3  critical=1  WCAG2AA=FAIL
[a11y-audit] PR_COMMENT  #42  violations=1
[a11y-audit] SKIP     ui:false story — audit not required
[a11y-audit] BLOCKED  merge blocked — 1 critical violation unresolved

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Generate a complete component file tree wired to design tokens with tests and Gherkin spec. Use when scaffolding a new UI component.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

kinncj/Heimdall702026년 9월 2일 업데이트

Extract Gherkin scenarios from story markdown files into runnable .feature files and generate step definition stubs. Use when syncing stories to test suites.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

kinncj/Heimdall702026년 9월 2일 업데이트

Read and write design tokens in W3C DTCG format (tokens.json) and emit CSS, Tailwind, and Mantine outputs. Use when modifying or generating design tokens.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

kinncj/Heimdall702026년 9월 2일 업데이트

Apply Docker and Docker Compose best practices for containerising services. Use when writing or reviewing Dockerfiles and compose configs.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

kinncj/Heimdall702026년 9월 2일 업데이트

Identify and annotate cloud cost implications of architectural decisions. Use when reviewing infrastructure changes or designing new services.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

kinncj/Heimdall702026년 9월 2일 업데이트

gh-issues

무료

Create, read, update, and link GitHub Issues as story artifacts throughout the story lifecycle. Use when managing issues for stories.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

kinncj/Heimdall702026년 9월 2일 업데이트

kinncj의 스킬 모두 보기

이 스킬의 문제 신고