composition-patterns (vercel-labs/agent-skills)
把 React 组件重构为复合组件和可复用 API,避免布尔属性不断增加。
composition-patterns:对照组 3.71、一句话提示 4.7、装 skill 4.39(0–5 分,Codex 实测,n=2/任务/组)。结论:视情况而定,相对不装 skill +0.68。
Codex CLI · gpt-6-astra
25.1 / 30
相对不装 skill 的提升:+0.68 · 看情况 · 18 次运行
视情况而定:提升 +0.68 低于 +1.0。推荐要求总分 22/30、产出质量 3.5、提升 +1.0。
前沿
通过使用 Codex 测试。
覆盖 Codex, Claude Code
Codex CLI 0.166(SOTA 档):18 次评分运行。按我们的分档规则,这档通过同时覆盖 Claude Code;它不代表第二档的结果。
分数
每一维 0 到 5 分,六维相加是满分 30 的总分。每一维都是 5 更好。成本 5 表示又便宜又快。安装 5 表示容易。
- 触发可靠性
- 5.0
- 相对不装的提升
- 2.7
- 产出质量
- 4.4
- 令牌与时间成本
- 5.0
- 安装与可移植性
- 4.0
- 安全
- 4.0
安全审查
安全静态扫描:0 CRITICAL / 0 HIGH / 0 MEDIUM。人工审查:可在断网沙箱中运行。扫描没有命中不等于绝对安全。
安装
npx skills@latest add vercel-labs/agent-skills --skill composition-patterns
固定提交: 1a2c79d4e6c4a2283dc2c614b6538cbe1ed5ca62
测试日期: 2026-11-02
我们测了什么
- Replace boolean props with composition — Composer.tsx has grown a pile of boolean props. Refactor it into a flexible component API that does not need them. Keep it in the single file Composer.tsx and keep the behaviour.
- Add a variant without adding props — Add a 'quick reply' composer: a one-line input that sends on Enter, with no attachments or emoji button. Do not add more boolean props to Composer. Edit Composer.tsx.
- Review a component API — Review the component API of Composer.tsx: what is wrong with it and what would you change? Do not change any files.
优点
- 平均分从不装时的 3.71 提到 4.39(+0.68)。
- 不点名也会被触发:Skill file read in 6/6 treatment runs.
- 不增加 token 开销:约为不装 skill 时的 1.03 倍。
缺点
- 一句话提示得 4.7,装 skill 得 4.39(差 -0.31)。
- 每个任务每组 n=2,差距在 1 分以内属于噪声。
评测
在 Codex CLI(SOTA 档)上跑 3 个任务、对照、一句话提示、装 skill 三组,共 18 次运行,由规则脚本打分(没有 LLM 评委),断网沙箱、每次运行独立临时目录。具体做法见下方英文评测原文。
三组对比
| 组别 | 平均分(0–5) | 每次 token |
|---|---|---|
| 不装 skill | 3.71 | 103k |
| 一句话提示 | 4.7 | 105k |
| 装了 skill | 4.39 | 105k |
Codex CLI 实测,18 次运行,规则脚本打分,没有 LLM 评委。总分 25.1 / 30,结论 视情况而定。
下面是评测原文,英文。
Verdict: Situational (25.1 / 30). Source: vercel-labs/agent-skills, skills/composition-patterns, pinned at commit 1a2c79d. Install: npx skills@latest add vercel-labs/agent-skills --skill composition-patterns. Licence: MIT (repo LICENSE).
What it is
The vercel-composition-patterns skill (vercel-labs/agent-skills): React composition guidance: avoid boolean prop proliferation, build compound components that share state through context (a generic state/actions interface), create explicit variant components, prefer children over renderX props, lift state into providers; includes React 19 notes. A SKILL.md plus a rules folder.
How we tested
Codex CLI 0.166 (gpt-6-astra, low reasoning) on a dedicated Windows test PC, a fresh temporary project and temporary HOME/TMPDIR/caches per run, skill installed (treatment) or absent (control) or absent with the skill's core requirement squeezed into one sentence appended to the prompt (one-liner group), 2 runs per group per task, network off, /tmp writes blocked. The prompts never mention the skill (except the wording noted below).
One chat-composer component (Composer.tsx) with a pile of boolean props. Tasks: refactor it into a flexible API keeping behaviour, add a 'quick reply' one-line composer without adding more booleans, and review the component API (no edits). Prompts do not name any pattern. The one-liner names the patterns (explicit variants from compound parts, shared context with state/actions, children instead of renderX, state lifted into a provider).
- Replace boolean props with composition — Composer.tsx has grown a pile of boolean props. Refactor it into a flexible component API that does not need them. Keep it in the single file Composer.tsx and keep the behaviour.
- Add a variant without adding props — Add a 'quick reply' composer: a one-line input that sends on Enter, with no attachments or emoji button. Do not add more boolean props to Composer. Edit Composer.tsx.
- Review a component API — Review the component API of Composer.tsx: what is wrong with it and what would you change? Do not change any files.
Scoring is programmatic (hidden tests / planted-issue recall / constraint checks / render+OCR where relevant), 0–5 per run. No LLM judge decided any score.
Results
The one-liner given to the third group: "Avoid boolean prop proliferation: split modes into explicit variant components built from compound parts that share a context with a generic state/actions interface, use children instead of renderX props, and lift state into a provider."
| Task | Control score | One-liner | With skill | Control tokens | One-liner tokens | With-skill tokens | Skill read |
|---|---|---|---|---|---|---|---|
| t1-refactor | 3.0 | 4.5 | 4.0 | 126k | 127k | 144k | 2/2 |
| t2-add-variant | 4.38 | 5.0 | 5.0 | 99k | 106k | 100k | 2/2 |
| t3-review | 3.75 | 4.58 | 4.17 | 81k | 80k | 70k | 2/2 |
Mean score: 3.71 without vs 4.39 with the skill (delta +0.68). Tokens per run: 103k vs 105k (1.03x); time 53s vs 49s. One-liner group: 4.7 (the skill's gain over the one-liner: -0.31; one-liner's own gain over no skill: +0.99); one-liner tokens 105k per run, so the skill costs 1.00x the one-liner.
What happened
- Control 3.71, skill 4.39, one-liner 4.70 (n=6 each). Without guidance, control's refactors did use compound parts but in both runs the shared context did not expose a state/actions interface, and in both quick-reply runs the new composer did not send on Enter.
- The skill's refactors had the context with state and actions (2/2) and its quick-reply composer sent on Enter (2/2). But both treatment refactors, like control, did not create explicit variant components (the one-liner runs did in 2/2), and in the review task both treatment runs and both control runs missed the 'boolean prop proliferation' point (a weak check pattern: reviewers may word it differently).
- Tokens are the same for skill, one-liner and control (105k, 105k, 103k): the skill read in 6/6 treatment runs without being named, and costs nothing extra.
- Treatment runs copy the rules folder into the project (14.7 new files per run).
Dimension scores
| Dimension | Score | Why |
|---|---|---|
| 1_output_quality | 4.39 | Mean score 4.39/5 with the skill (programmatic checks, see per-task table). |
| 2_lift_vs_no_skill | 2.68 | +0.68 points vs 3.71 without the skill (n=2 per task; under ~1 point is noise). |
| 3_trigger | 5.0 | Skill file read in 6/6 treatment runs. |
| 4_instructions_portability | 4 | SKILL.md plus an AGENTS.md index and a rules folder; docs only. |
| 5_efficiency | 5 | 1.03x tokens (105k vs 103k per run) and 0.93x time (49s vs 53s). |
| 6_safety_hygiene | 4 | Prose only; see security.md. |
Limits of this test
- Rule-based checks on code text (context/provider patterns, variant components, key props); the TypeScript was not compiled or rendered (React is not installed in the sandbox).
- One check for the review task is weak: it looks for the literal 'boolean prop' wording, which reviewers can express differently.
- Single component fixture; n=2 per cell.
- n = 2 per group per task: differences under about 1 point are noise.
- Rule-based scoring measures what the checks can see; it cannot judge taste or whether a result is the best possible.
Security
See security.md. Scan: 0 CRITICAL / 0 HIGH / 0 MEDIUM. Verdict: safe to run (no-network sandbox). A clean scan does not prove a skill safe.
Bottom line
A decent pattern guide: the compound-component structure appears reliably with the skill and rarely without it, for free. A single sentence naming the patterns did better here, so the skill's benefit is convenience and breadth, not a quality ceiling.
记录
Run log for composition-patterns: Codex CLI, network off, per-run temp HOME/TMPDIR, one row per graded run. Columns: task, arm-run, score (0-5), tokens, seconds, checks passed, skill files the agent opened. t1-refactor control-1: score 3.0, tokens 130032, secs 72.5, checks 3/5, opened nothing t1-refactor control-2: score 3.0, tokens 123317, secs 64.3, checks 3/5, opened nothing t1-refactor oneliner-1: score 4.0, tokens 127545, secs 101.8, checks 4/5, opened nothing t1-refactor oneliner-2: score 5.0, tokens 127024, secs 89.6, checks 5/5, opened nothing t1-refactor treatment-1: score 4.0, tokens 158276, secs 67.7, checks 4/5, opened composition-patterns t1-refactor treatment-2: score 4.0, tokens 130234, secs 59.2, checks 4/5, opened composition-patterns t2-add-variant control-1: score 4.38, tokens 99946, secs 40.3, checks 3/4, opened nothing t2-add-variant control-2: score 4.38, tokens 100013, secs 39.2, checks 3/4, opened nothing t2-add-variant oneliner-1: score 5.0, tokens 106946, secs 70.9, checks 4/4, opened nothing
18 次运行、3 个产物
样例产物
前沿 · 装了 skill:Composer.tsx(t1-refactor,第 1 次,Codex)
import React, { createContext, use, useState } from "react"; type ComposerProps = { initialText?: string; onSubmit: (text: string) => void; children?: React.ReactNode; }; type ComposerContextValue = { state: { text: string }; actions: { setText: (text: string) => void; submit: () => void; }; }; const ComposerContext = createContext<ComposerContextValue | null>(null); export function useComposer() { const context = use(ComposerContext); if (!context) { throw new Error("Composer components must be inside Composer.Provider or Composer."); } return context; } function ComposerProvider({ initialText = "", onSubmit, children }: ComposerProps) { const [text, setText] = useState(initialText); function submit() { onSubmit(text); setText(""); } return ( <ComposerContext value={{ state: { text }, actions: { setText, submit } }}> {children} </ComposerContext> ); } function ComposerFrame({ children }: { children: React.ReactNode }) { const { actions } = useComposer(); return ( <form onSubmit={(event) => { event.preventDefault(); actions.submit(); }}> {children} </form> ); } function ComposerInput({ placeholder = "Message" }: { placeholder?: string }) { const { state, actions } = useComposer(); return ( <textarea value={state.text} onChange={(event) => actions.setText(event.target.value)} placeholder={placeholder} /> ); } function ComposerFormatting() { return ( <div className="toolbar"> <button type="button">B</button> <button type="button">I</button> </div> ); } function ComposerAttachments() { return <button type="button">Attach</button>; } function ComposerEmojis() { return <button type="button">😀</button>; } function ComposerSubmit({ children = "Send" }: { children?: React.ReactNode }) { return <button type="submit">{children}</button>; } function ComposerRoot({ children, ...props }: ComposerProps) { return ( <ComposerProvider {...props}> <ComposerFrame> {children === undefined ? <><ComposerInput /><ComposerSubmit /></> : children} </ComposerFrame> </ComposerProvider> ); } /** * Compose controls in their display order; footer content is ordinary children: * * <Composer initialText="Draft" onSubmit={send}> * <Composer.Formatting /> * <Composer.Input placeholder="Reply in thread" /> * <Composer.Attachments /> * <Composer.Emojis /> * <Composer.Submit /> * <CustomFooter /> * </Composer> * * Message: use the default input placeholder and submit label. * Thread: use "Reply in thread" and "Send". * Forward: use "Add a note" and "Forward". * Edit: use "Edit message" and "Save", and omit Attachments. * When migrating overlapping modes, edit takes precedence over forward, then thread. * Include Formatting, Attachments, and Emojis only where needed. * Provider and Frame can also be composed separately to share state with siblings. */ export const Composer = Object.assign(ComposerRoot, { Provider: ComposerProvider, Frame: ComposerFrame, Input: ComposerInput, Formatting: ComposerFormatting, Attachments: ComposerAttachments, Emojis: [... excerpt: first 3200 of 3247 characters]