--- version: "1.0.0" evaluation: programmatic agent: claude-code model: claude-sonnet-4-6 snapshot: python312-uv origin: url: "https://skills.sh/vercel-labs/agent-skills/vercel-composition-patterns" source_host: "skills.sh" source_title: "React Composition Patterns" imported_at: "2026-05-01T00:00:00Z" imported_by: "skill-to-runbook-converter@1.0.0" attribution: collection_or_org: "vercel-labs" skill_name: "vercel-composition-patterns" confidence: "high" secrets: {} --- # React Composition Patterns — Agent Runbook ## Objective Apply React composition patterns to build flexible, maintainable React components that scale. This runbook operationalizes the `vercel-composition-patterns` skill: it audits a target codebase for boolean-prop proliferation, identifies components that violate composition principles, produces refactored examples using compound components and context providers, and emits a structured report. It covers four rule categories — Component Architecture, State Management, Implementation Patterns, and React 19 APIs — with priority-ordered analysis and actionable recommendations. ## REQUIRED OUTPUT FILES (MANDATORY) **You MUST write all of the following files to `/app/results`. The task is NOT complete until every file exists and is non-empty.** | File | Description | |------|-------------| | `/app/results/audit_report.md` | Full analysis: components reviewed, violations found, recommendations | | `/app/results/refactored_examples.md` | Before/after code examples for each flagged component | | `/app/results/rule_coverage.json` | Which rules fired, how many times, per-rule violation counts | | `/app/results/summary.md` | Executive summary: total violations, top issues, action items | | `/app/results/validation_report.json` | Structured validation results with stages and `overall_passed` | ## Parameters | Parameter | Value | Description | |-----------|-------|-------------| | Results directory | `/app/results` | Output directory for all results | | Target codebase | *(required)* | Path or glob to the React component files to audit | | React version | `18` or `19` | Determines whether React 19 API rules apply | | Severity threshold | `MEDIUM` | Minimum rule priority to report (HIGH, MEDIUM, LOW) | ## Dependencies | Dependency | Type | Required | Description | |------------|------|----------|-------------| | `node` / `npx` | CLI | Yes | Required to run skills tooling | | `git` | CLI | Yes | Read file history and blame for context | | Python 3.12+ | Runtime | Yes | Script execution environment | | `pyyaml` | Python package | Yes | Parse YAML frontmatter in rule files | | `markdown-it-py` | Python package | Yes | Parse markdown rule definitions | ## Step 1: Environment Setup ```bash # Verify Node.js and npm are available command -v node >/dev/null || { echo "ERROR: node not installed"; exit 1; } command -v npx >/dev/null || { echo "ERROR: npx not installed"; exit 1; } command -v git >/dev/null || { echo "ERROR: git not installed"; exit 1; } # Install Python dependencies pip install pyyaml markdown-it-py # Create output directory mkdir -p /app/results # Verify target codebase path is set if [ -z "$TARGET_CODEBASE" ]; then echo "ERROR: TARGET_CODEBASE environment variable is not set" exit 1 fi echo "Auditing: $TARGET_CODEBASE" ``` ## Step 2: Load Rule Definitions Fetch the rule set from the `vercel-labs/agent-skills` GitHub repository and load all rule files from the `skills/composition-patterns/rules/` directory. ```python import requests, json, pathlib RULES_API = "https://api.github.com/repos/vercel-labs/agent-skills/contents/skills/composition-patterns/rules" headers = {"User-Agent": "jetty-runbook/1.0", "Accept": "application/vnd.github.v3+json"} r = requests.get(RULES_API, headers=headers, timeout=30) r.raise_for_status() rule_files = r.json() rules = {} for rf in rule_files: if rf["name"].endswith(".md"): raw = requests.get(rf["download_url"], headers=headers, timeout=30) raw.raise_for_status() rules[rf["name"]] = raw.text print(f"Loaded rule: {rf['name']}") pathlib.Path("/app/results/work").mkdir(parents=True, exist_ok=True) pathlib.Path("/app/results/work/rules.json").write_text(json.dumps( {k: v[:200] for k, v in rules.items()}, indent=2 )) print(f"Loaded {len(rules)} rules") ``` Rules are organized into four priority categories: | Priority | Category | Impact | Prefix | |----------|----------|--------|--------| | 1 | Component Architecture | HIGH | `architecture-` | | 2 | State Management | MEDIUM | `state-` | | 3 | Implementation Patterns | MEDIUM | `patterns-` | | 4 | React 19 APIs | MEDIUM | `react19-` | ## Step 3: Scan Codebase for Violations Apply each rule to every `.tsx` / `.jsx` / `.js` component file in the target codebase. Record violations with file path, line number, rule ID, and severity. ```python import os, re, glob, json, pathlib TARGET = os.environ.get("TARGET_CODEBASE", ".") REACT_VERSION = int(os.environ.get("REACT_VERSION", "18")) component_files = ( glob.glob(f"{TARGET}/**/*.tsx", recursive=True) + glob.glob(f"{TARGET}/**/*.jsx", recursive=True) + glob.glob(f"{TARGET}/**/*.js", recursive=True) ) component_files = [f for f in component_files if "node_modules" not in f] violations = [] BOOLEAN_PROP_PATTERN = re.compile( r"(?:interface|type)\s+\w+Props\s*[={][^}]*\b(is[A-Z]\w+|has[A-Z]\w+|show[A-Z]\w+|hide[A-Z]\w+|enable[A-Z]\w+|disable[A-Z]\w+)\s*[?:]?\s*boolean", re.DOTALL ) FORWARD_REF_PATTERN = re.compile(r"\bforwardRef\b") for fpath in component_files: try: src = pathlib.Path(fpath).read_text(errors="replace") except Exception: continue # architecture-avoid-boolean-props for m in BOOLEAN_PROP_PATTERN.finditer(src): line = src[:m.start()].count("\n") + 1 violations.append({ "file": fpath, "line": line, "rule": "architecture-avoid-boolean-props", "severity": "HIGH", "match": m.group(1) }) # react19-no-forwardref (only flag if React 19) if REACT_VERSION >= 19: for m in FORWARD_REF_PATTERN.finditer(src): line = src[:m.start()].count("\n") + 1 violations.append({ "file": fpath, "line": line, "rule": "react19-no-forwardref", "severity": "MEDIUM", "match": "forwardRef usage" }) print(f"Scanned {len(component_files)} files, found {len(violations)} violations") pathlib.Path("/app/results/work/violations.json").write_text(json.dumps(violations, indent=2)) ``` ## Step 4: Generate Audit Report Produce `audit_report.md` and `rule_coverage.json` from the violations found in Step 3. ```python import json, pathlib from collections import defaultdict violations = json.loads(pathlib.Path("/app/results/work/violations.json").read_text()) # Build rule coverage rule_counts = defaultdict(int) for v in violations: rule_counts[v["rule"]] += 1 rule_coverage = { "total_violations": len(violations), "by_rule": dict(rule_counts), "files_affected": len(set(v["file"] for v in violations)) } pathlib.Path("/app/results/rule_coverage.json").write_text(json.dumps(rule_coverage, indent=2)) # Build audit report lines = ["# React Composition Patterns — Audit Report\n"] lines.append(f"**Total violations:** {len(violations)}\n") lines.append(f"**Files affected:** {rule_coverage['files_affected']}\n\n") lines.append("## Violations by Rule\n") for rule, count in sorted(rule_counts.items(), key=lambda x: -x[1]): lines.append(f"- `{rule}`: {count} occurrence(s)\n") lines.append("\n") lines.append("## Detailed Findings\n") for v in violations: lines.append(f"- **{v['file']}:{v['line']}** — `{v['rule']}` ({v['severity']}): {v['match']}\n") pathlib.Path("/app/results/audit_report.md").write_text("".join(lines)) print("audit_report.md written") ``` ## Step 5: Generate Refactored Examples For each flagged pattern, produce a before/after refactoring example following the composition patterns described in the source skill. ```python import json, pathlib violations = json.loads(pathlib.Path("/app/results/work/violations.json").read_text()) examples = ["# Refactored Examples\n\n"] if any(v["rule"] == "architecture-avoid-boolean-props" for v in violations): examples.append("""## architecture-avoid-boolean-props ### Before (boolean props — avoid) ```tsx // ❌ Boolean prop proliferation interface ButtonProps { isPrimary?: boolean; isDisabled?: boolean; isLoading?: boolean; } function Button({ isPrimary, isDisabled, isLoading }: ButtonProps) { return ( ); } ``` ### After (explicit variants — prefer) ```tsx // ✅ Explicit variant components function PrimaryButton({ disabled, children }: { disabled?: boolean; children: React.ReactNode }) { return ; } function SecondaryButton({ disabled, children }: { disabled?: boolean; children: React.ReactNode }) { return ; } function LoadingButton({ children }: { children: React.ReactNode }) { return ; } ``` """) if any(v["rule"] == "react19-no-forwardref" for v in violations): examples.append("""## react19-no-forwardref ### Before (forwardRef — React 18 style) ```tsx // ❌ forwardRef is removed in React 19 const Input = React.forwardRef((props, ref) => ( )); ``` ### After (ref as prop — React 19 style) ```tsx // ✅ Pass ref directly as a prop in React 19 function Input({ ref, ...props }: InputProps & { ref?: React.Ref }) { return ; } ``` """) if not violations: examples.append("No violations found — codebase follows composition patterns correctly.\n") pathlib.Path("/app/results/refactored_examples.md").write_text("".join(examples)) print("refactored_examples.md written") ``` ## Step 6: Iterate on Errors (max 3 rounds) If Step 4 or 5 produced empty output files or scanning errors: 1. Check `/app/results/work/violations.json` for parse errors 2. Verify the `TARGET_CODEBASE` path is correct and files are accessible 3. Re-run the failing step with corrected paths 4. Repeat up to 3 times total ### Common Fixes | Issue | Fix | |-------|-----| | No component files found | Verify `TARGET_CODEBASE` glob and check for `.tsx`/`.jsx` extensions | | Rule files API rate limited | Add `Authorization: token $GITHUB_PAT` header to GitHub API calls | | Empty violations but known issues exist | Broaden regex patterns or add manual review entries | | `audit_report.md` missing violations | Re-run Step 4 after confirming `violations.json` is non-empty | ## Step 7: Write Summary ```python import json, pathlib rule_coverage = json.loads(pathlib.Path("/app/results/rule_coverage.json").read_text()) summary = f"""# React Composition Patterns — Summary ## Overview - **Total violations**: {rule_coverage['total_violations']} - **Files affected**: {rule_coverage['files_affected']} - **Rules applied**: architecture-avoid-boolean-props, architecture-compound-components, state-decouple-implementation, state-context-interface, state-lift-state, patterns-explicit-variants, patterns-children-over-render-props, react19-no-forwardref ## Top Issues """ by_rule = rule_coverage.get("by_rule", {}) if by_rule: for rule, count in sorted(by_rule.items(), key=lambda x: -x[1])[:5]: summary += f"- `{rule}`: {count} violation(s)\\n" else: summary += "- No violations detected\\n" summary += """ ## Recommended Actions 1. Review flagged components in `audit_report.md` 2. Apply refactoring patterns from `refactored_examples.md` 3. For compound components, introduce a shared context provider 4. Replace boolean props with explicit variant components 5. If on React 19, migrate `forwardRef` usage to direct ref props ## Provenance - Skill: vercel-composition-patterns by vercel-labs/agent-skills - Origin: https://skills.sh/vercel-labs/agent-skills/vercel-composition-patterns - Runbook: skill-to-runbook-converter v1.0.0 """ pathlib.Path("/app/results/summary.md").write_text(summary) print("summary.md written") ``` ## Final Checklist (MANDATORY — do not skip) ### Verification Script ```bash echo "=== FINAL OUTPUT VERIFICATION ===" RESULTS_DIR="/app/results" for f in \ "$RESULTS_DIR/audit_report.md" \ "$RESULTS_DIR/refactored_examples.md" \ "$RESULTS_DIR/rule_coverage.json" \ "$RESULTS_DIR/summary.md" \ "$RESULTS_DIR/validation_report.json"; do if [ ! -s "$f" ]; then echo "FAIL: $f is missing or empty" else echo "PASS: $f ($(wc -c < "$f") bytes)" fi done echo "=== VERIFICATION COMPLETE ===" ``` ### Checklist - [ ] `audit_report.md` lists all violations with file, line, rule, and severity - [ ] `refactored_examples.md` shows before/after for each rule that fired - [ ] `rule_coverage.json` has `total_violations`, `by_rule`, and `files_affected` - [ ] `summary.md` has overview, top issues, and recommended actions - [ ] `validation_report.json` exists with `stages`, `results`, and `overall_passed` - [ ] Verification script printed PASS for every output file **If ANY item fails, go back and fix it. Do NOT finish until all items pass.** ## Tips - **React 19 rules are opt-in.** Set `REACT_VERSION=19` to enable the `react19-*` rules; they are silently skipped for React 18 projects to avoid false positives. - **Boolean prop detection is heuristic.** The regex catches common naming conventions (`isX`, `hasX`, `showX`) but may miss custom patterns — review the audit output manually for completeness. - **Compound components need context.** When refactoring a boolean-prop-heavy component, first identify which props relate to shared state, then extract a context provider. - **`children` over render props.** Prefer `children` composition over `renderHeader`, `renderFooter` patterns — it is more idiomatic in modern React. - **Start with HIGH severity.** Tackle `architecture-*` rules first; they have the highest impact on long-term maintainability.