Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/expo-skill-eval/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Read `references/runtime-matrix.md` to find the skill's default mode before sugg

Pass the choice to the snapshot scripts via the `EXPO_SKILL_EVAL_RUNNER` env var (`expo-go` default, or `dev-build`), and reflect it in each eval case's `runtime.mode` (`expo-go` or `dev-build`). See step 5.

**Pick the platforms — always ask, regardless of skill.** Offer iOS / Android / web (multi-select) with `AskUserQuestion`; default to iOS + Android, but always present web as an option — don't pre-filter by skill. **Web is a valid choice for most skills**: `@expo/ui`'s *universal* components (`Host`, `Row`, `Column`, `Button`, `List`, …) render on web, as do `use-dom`, NativeWind/Tailwind, API routes, and plain React Native. The only thing that won't show on web is a *platform-specific* native tree (`@expo/ui/swift-ui` or `@expo/ui/jetpack-compose`), which renders blank there — and that blank is itself a useful signal, so it's still the user's call. Web runs via `snapshot-web.sh` (`expo start --web` + Playwright/Chromium) **regardless of the runner** (`expo run` is native-only; there's no web dev build), and it's the least-exercised path. Write the chosen set into each eval case's `runtime.platforms` and have `run_snapshots.py` loop them.
**Pick the platforms — always ask, regardless of skill.** Offer iOS / Android / web (multi-select) with `AskUserQuestion`; default to iOS + Android, but always present web as an option — don't pre-filter by skill. **Web is a valid choice for most skills**: `@expo/ui`'s *universal* components (`Host`, `Row`, `Column`, `Button`, `List`, …) render on web, as do `expo-dom`, NativeWind/Tailwind, API routes, and plain React Native. The only thing that won't show on web is a *platform-specific* native tree (`@expo/ui/swift-ui` or `@expo/ui/jetpack-compose`), which renders blank there — and that blank is itself a useful signal, so it's still the user's call. Web runs via `snapshot-web.sh` (`expo start --web` + Playwright/Chromium) **regardless of the runner** (`expo run` is native-only; there's no web dev build), and it's the least-exercised path. Write the chosen set into each eval case's `runtime.platforms` and have `run_snapshots.py` loop them.

**Confirm how `claude -p` subprocesses run — once, before starting.** Ask with `AskUserQuestion` whether they may run with `--dangerously-skip-permissions`, then apply the same answer to every subprocess this run (never re-prompt mid-run):

Expand Down
20 changes: 10 additions & 10 deletions .claude/skills/expo-skill-eval/references/runtime-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ Modes:

| Skill | Mode | Platforms | Notes |
|-------|------|-----------|-------|
| building-native-ui | expo-go | ios, android | Core target for visual evals. RN primitives + styling/controls/media/animations all work in Expo Go. |
| expo-router | expo-go | ios, android | Navigation/routing target: routes, links, native stacks, modals, sheets, headers. Native tabs sections may need a dev build — verify per case. |
| expo-native-ui | expo-go | ios, android | Core target for visual evals. RN primitives + styling/controls/media/animations all work in Expo Go. |
| expo-tailwind-setup | expo-go | ios, android, web | NativeWind v5 / react-native-css are JS-level; works in Expo Go. Good web candidate too. |
| native-data-fetching | expo-go | ios, android | Fetch/React Query/SWR are pure JS. Mock or use stable public endpoints so evals are deterministic. |
| use-dom | expo-go | ios, android, web | DOM components run in a webview on native (Expo Go, SDK 52+) and as-is on web. Allow extra settle time for the webview to paint. |
| expo-api-routes | static-only + HTTP | — | No screenshot: while Metro runs, `curl` the route and save the response as an output file for grading. |
| expo-data-fetching | expo-go | ios, android | Fetch/React Query/SWR are pure JS. Mock or use stable public endpoints so evals are deterministic. |
| expo-dom | expo-go | ios, android, web | DOM components run in a webview on native (Expo Go, SDK 52+) and as-is on web. Allow extra settle time for the webview to paint. |
| eas-hosting | static-only + HTTP | — | No screenshot: while Metro runs, `curl` the route and save the response as an output file for grading. |
| expo-ui (merged swift-ui + jetpack-compose) | expo-go | ios, android, web | `@expo/ui` ships in the SDK 56 default template (~56.0.17) and its native code is included in Expo Go 56 — verified rendering 2026-06: a SwiftUI Form renders in Expo Go on the iOS simulator. The **universal** components (`Host`, `Row`, `Column`, `Button`, `List`, …) also render on **web**; only the platform-specific `@expo/ui/swift-ui` (SwiftUI) and `@expo/ui/jetpack-compose` (Compose) trees are native-only and render blank on web. Pick platforms by what the eval case uses. |
| expo-module | static-only | — | Custom native modules can't load in Expo Go. Gate on TS compile of the module API; native compilation (prebuild + xcodebuild/gradle) is possible but expensive — out of scope for now. |
| add-app-clip | static-only | — | App Clip targets require prebuild + Xcode. Assert on config-plugin output instead. |
| expo-app-clip | static-only | — | App Clip targets require prebuild + Xcode. Assert on config-plugin output instead. |
| expo-brownfield | static-only | — | Brownfield integration has no Expo Go story. Assert on generated integration files. |
| expo-dev-client | n/a | — | Output is build/distribution workflow, not renderable app code. |
| expo-deployment | n/a | — | Deployment guidance; assert on transcript/config content only. |
| expo-cicd-workflows | n/a | — | Output is `.eas/workflows/` YAML; assert on YAML content (and `workflow_validate` via the Expo MCP if available). |
| upgrading-expo | static-only | — | An upgrade eval can run the static gate on the upgraded fixture — a strong signal. Needs a fixture pinned to an *older* SDK (pass e.g. `55` to make-fixture.sh). |
| eas-app-stores | n/a | — | Deployment guidance; assert on transcript/config content only. |
| eas-workflows | n/a | — | Output is `.eas/workflows/` YAML; assert on YAML content (and `workflow_validate` via the Expo MCP if available). |
| expo-upgrade | static-only | — | An upgrade eval can run the static gate on the upgraded fixture — a strong signal. Needs a fixture pinned to an *older* SDK (pass e.g. `55` to make-fixture.sh). |
| eas-update-insights | n/a | — | CLI/metrics interpretation; no app code output. |
| expo-observe | expo-go (verify) | ios, android | `expo-observe` instrumentation is JS-level, but confirm the package works in Expo Go before relying on screenshots; fall back to static-only. |
| eas-observe | expo-go (verify) | ios, android | `expo-observe` instrumentation is JS-level, but confirm the package works in Expo Go before relying on screenshots; fall back to static-only. |

Entries marked "verify" haven't been validated against a real run yet — confirm on first use and update this table.

## Image-prompt (clone-this) candidates

Image prompts (`reference_image` — build an app to match a target screenshot, graded by `reference_match` + `references/design-rubric.md`) are most informative for the **visual** skills — `building-native-ui`, `expo-ui`, `expo-tailwind-setup`, `use-dom` — where reproducing a specific UI is the point. Pair them with "Runtime + screenshots" so the harness captures the generated app to compare against the target. They add nothing for `n/a` skills (no app UI).
Image prompts (`reference_image` — build an app to match a target screenshot, graded by `reference_match` + `references/design-rubric.md`) are most informative for the **visual** skills — `expo-router`, `expo-ui`, `expo-tailwind-setup`, `expo-dom` — where reproducing a specific UI is the point. Pair them with "Runtime + screenshots" so the harness captures the generated app to compare against the target. They add nothing for `n/a` skills (no app UI).
25 changes: 18 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ plugins/
.mcp.json # Claude Code and Codex MCP server configuration
mcp.json # Cursor MCP server configuration
skills/
README.md # Grouped index of all skills
skill-name/
SKILL.md # Main skill file
references/ # Optional supporting documentation
scripts/ # Optional utility scripts
agents/
openai.yaml # Codex trigger metadata
README.md # Plugin documentation
README.md # User-facing installation instructions
CONTRIBUTING.md # Contributor guidance
CONTRIBUTING.md # Contributor guidance (adding skills, naming, free vs paid)
skills.sh.json # skills.sh catalog groupings
scripts/ # CI checks (skill limits, plugin version bump)
```

The Claude Code marketplace currently exposes `expo` as the active plugin. It also keeps deprecated aliases such as `expo-app-design`, `upgrading-expo`, and `expo-deployment` pointing at `./plugins/expo` for backward compatibility. The Codex and Cursor marketplaces expose only the active `expo` plugin because their marketplace entries must match the plugin manifest name.
Expand Down Expand Up @@ -89,7 +94,8 @@ Frontmatter fields:

Skill guidelines:

- Keep `SKILL.md` focused and under 500 lines when practical.
- Name skills `expo-*` (open-source framework) or `eas-*` (paid EAS service), and prefix the description with `Framework (OSS).` or `EAS service (paid).` to match. Paid skills open the body with a costs/plan-limits callout. See `CONTRIBUTING.md` for the full rules.
- Keep the `SKILL.md` body under 500 lines and the `description` under 1024 characters - both are CI-enforced by `scripts/check-skill-limits.ts`.
- Move detailed material to `references/` and load it only when the skill needs it.
- Put reusable validation or fetching logic in `scripts/` instead of pasting large command blocks into the skill.
- Write descriptions that match how users naturally ask for help.
Expand Down Expand Up @@ -188,11 +194,14 @@ When changing Claude Code marketplace aliases, preserve backward compatibility u

## Adding a Skill

1. Create `plugins/expo/skills/my-skill/SKILL.md`.
2. Add focused reference files under `plugins/expo/skills/my-skill/references/` when the skill needs more detail than belongs in the main `SKILL.md`.
3. Add scripts under `plugins/expo/skills/my-skill/scripts/` only for reusable logic.
4. Update `plugins/expo/README.md` or the root `README.md` only when the user-facing installation or usage story changes.
5. Keep the skill under the existing `expo` plugin unless there is a clear distribution reason to create a new plugin.
Follow the full guide in `CONTRIBUTING.md`. In short:

1. Pick the name per the naming rule: `expo-*` for open-source framework skills, `eas-*` for paid EAS service skills.
2. Create `plugins/expo/skills/<skill-name>/SKILL.md` with the category-prefixed description (`Framework (OSS).` or `EAS service (paid).`); paid skills open with a costs/plan-limits callout.
3. Add focused reference files under `references/` when the skill needs more detail than belongs in the main `SKILL.md`, scripts under `scripts/` only for reusable logic, and `agents/openai.yaml` for Codex triggering.
4. Register the skill in every catalog: `skills.sh.json`, `plugins/expo/README.md`, `plugins/expo/skills/README.md`, and the root `README.md`.
5. Bump the version in all three plugin manifests together (they must match and be greater than main; CI-enforced).
6. Keep the skill under the existing `expo` plugin unless there is a clear distribution reason to create a new plugin.

## Testing Plugins

Expand All @@ -201,6 +210,8 @@ Validate the changed surface before publishing:
```bash
claude plugin validate .
claude plugin validate ./plugins/expo
bun scripts/check-skill-limits.ts
bun scripts/check-plugin-version-bump.ts origin/main
```

For JSON-only changes, also verify the edited JSON file parses:
Expand Down
129 changes: 129 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,132 @@
# Contributing

- Use your skills locally first to build something meaningful before contributing.

## Adding a new skill

Skills teach an agent how to do one Expo task well. Follow these guidelines so new
skills stay consistent with the rest of the marketplace.

### 1. Scaffold it with `skill-creator`

From Claude Code, run the `/skill-creator` skill to scaffold a new skill - and to edit, optimize,
or eval an existing one. It sets up the folder structure and helps you tune the `description` for
reliable triggering.

Skills live one level deep:

```
plugins/expo/skills/<skill-name>/
SKILL.md # required
references/ # optional, loaded on demand
scripts/ # optional, reusable logic
agents/openai.yaml # Codex trigger metadata
```

### 2. Name it `expo-*` or `eas-*`

Names are lowercase kebab-case (max 64 chars). The prefix signals the free/paid boundary:

- **`expo-*`** - open-source framework skills: the Expo SDK, Expo Router, React Native, and
local tooling. Examples: `expo-router`, `expo-dom`, `expo-module`, `expo-web-to-native`.
- **`eas-*`** - skills whose core purpose is a paid Expo Application Services product.
Examples: `eas-hosting`, `eas-app-stores`, `eas-workflows`, `eas-observe`, `eas-simulator`.

Name after what users say, and prefer the real product or package name (e.g. `eas-hosting`,
not `expo-api-routes`). To rename an existing skill, `git mv` the directory (to preserve
history), update the frontmatter `name:`, and fix every reference (catalogs, `agents/openai.yaml`,
cross-links in other skills).

### 3. Decide free vs paid - and be strict about it

A skill belongs in **Services & paid distribution** only if its **core purpose requires a paid
EAS product** (EAS Build, Submit, Hosting, Update, Workflows, Observe, Simulator, …).

- A paid **Apple Developer or Google Play** account does **not** make a skill "paid" here - that
is app-store distribution, not EAS. `expo-app-clip` is a framework skill even though shipping a
Clip needs an Apple account.
- If authoring is free and only *deploying* is paid, it is usually still framework - unless the
skill is fundamentally about the paid service. (`eas-hosting` is the deploy skill, so it is
paid; free API-route authoring lives inside it.)
- Litmus test: *can a developer get the main value of this skill without paying Expo?*
Yes → framework (`expo-*`). No → paid (`eas-*`).

### 4. Prefix the description, and add a costs note for paid skills

Every `description` opens with its category label:

- Framework: `Framework (OSS). <what it does and when to use it>`
- Paid: `EAS service (paid). <what it does and when to use it>`

Paid skills also open the `SKILL.md` **body** with a short callout:

```markdown
> **EAS service - costs apply.** <one line on what consumes the plan>. See https://expo.dev/pricing.
```

### 5. Write a description that triggers well

The `description` is how an agent decides to load the skill, so it matters more than any other field.

- **Max 1024 characters** (CI-enforced).
- Say **what it does** *and* **when to use it**, in the words users actually type ("turn a website
into an app", "run on a cloud simulator", "ship to TestFlight").
- Use concrete package, command, and API names (`@expo/ui`, `eas deploy`, `+api.ts`) - but don't
keyword-stuff with unrelated terms.
- When a sibling skill is easy to confuse with, add a `Not for …` clause (see `expo-ui` vs
`expo-router`).

### 6. Keep it concise - context windows are the constraint

When a skill triggers, its whole `SKILL.md` loads into the agent's context. Shorter is better.

- The body is capped at **500 lines** (CI-enforced) - treat that as a ceiling, not a target.
- Move anything the agent needs only *sometimes* into `references/*.md` and link to it; references
load on demand.
- Put reusable validation or fetching logic in `scripts/` instead of long inline command blocks.
- One skill = one job. If it grows two distinct triggers, split it (that is why `eas-hosting` and
`eas-app-stores` are separate skills).

### 7. Add the Codex agent file

Add `agents/openai.yaml` with `display_name`, `short_description` (paid skills prefix it
`Paid EAS service.`), and a `default_prompt` that references the skill via `$<skill-name>`.

### 8. Register the skill in every catalog

Add it to the correct group (framework vs paid) in all of:

- `skills.sh.json`
- `plugins/expo/README.md` - **What This Plugin Does**, **When to Use**, and **Skills Included**
- `plugins/expo/skills/README.md`
- `README.md`

### 9. Bump the plugin version

Bump `version` in **all three** manifests together - they must match each other and be greater
than `main`. CI enforces this.

- `plugins/expo/.claude-plugin/plugin.json`
- `plugins/expo/.codex-plugin/plugin.json`
- `plugins/expo/.cursor-plugin/plugin.json`

### 10. Validate before opening a PR

```bash
claude plugin validate ./plugins/expo
bun scripts/check-skill-limits.ts
bun scripts/check-plugin-version-bump.ts origin/main
```

Also run `python3 -m json.tool <file>` on any JSON you edited, and if the skill ships `scripts/`,
run that skill's own validation.

`check-skill-limits.ts` enforces more than the size caps: the naming rule (step 2), the category
prefixes and paid costs callout (step 4), the Codex agent file and its paid prefix (step 7), and
the `skills.sh.json` grouping (step 8) all fail CI when violated.

### Conventions

- MIT license for every skill; use `@expo.io` or `@expo.dev` author emails.
- Keep `references/` next to the skill that uses them.
- Don't broaden a skill's scope or trigger intent when editing it - keep changes focused.
Loading
Loading