From 5a6dc9e8db3bdfc4cbe853d91b52df0000dcd071 Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Mon, 29 Jun 2026 22:39:20 +0800 Subject: [PATCH] add expo-overview skill --- .github/workflows/skill-routing-coverage.yml | 84 ++++++++++++++++++ AGENTS.md | 5 +- README.md | 8 ++ plugins/expo/README.md | 4 + plugins/expo/skills/README.md | 8 ++ plugins/expo/skills/expo-native-ui/SKILL.md | 3 + .../expo-native-ui/references/controls.md | 2 + plugins/expo/skills/expo-overview/SKILL.md | 85 +++++++++++++++++++ .../skills/expo-overview/agents/openai.yaml | 4 + plugins/expo/skills/expo-ui/SKILL.md | 58 +++++++++++-- .../skills/expo-ui/references/universal.md | 27 ++++++ scripts/check-overview-routing.ts | 59 +++++++++++++ skills.sh.json | 1 + 13 files changed, 340 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/skill-routing-coverage.yml create mode 100644 plugins/expo/skills/expo-overview/SKILL.md create mode 100644 plugins/expo/skills/expo-overview/agents/openai.yaml create mode 100644 scripts/check-overview-routing.ts diff --git a/.github/workflows/skill-routing-coverage.yml b/.github/workflows/skill-routing-coverage.yml new file mode 100644 index 0000000..efb3b0e --- /dev/null +++ b/.github/workflows/skill-routing-coverage.yml @@ -0,0 +1,84 @@ +name: skill routing coverage + +on: + pull_request: + branches: + - main + merge_group: + branches: + - main + +concurrency: + group: skill-routing-coverage-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + issues: write + +jobs: + check: + name: check expo-overview routing + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Set up Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + + - name: Check expo-overview routing coverage + id: routing-check + continue-on-error: true + env: + ROUTING_CHECK_SUMMARY_PATH: .context/skill-routing-check-summary.md + run: bun scripts/check-overview-routing.ts + + - name: Add workflow summary + if: always() && hashFiles('.context/skill-routing-check-summary.md') != '' + run: cat .context/skill-routing-check-summary.md >> "$GITHUB_STEP_SUMMARY" + + - name: Comment on pull request + if: always() && github.event_name == 'pull_request' && hashFiles('.context/skill-routing-check-summary.md') != '' + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const fs = require("node:fs"); + + const marker = ""; + const summary = fs.readFileSync(".context/skill-routing-check-summary.md", "utf8"); + const body = `${marker}\n${summary}`; + const issue_number = context.payload.pull_request.number; + const { owner, repo } = context.repo; + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number, + per_page: 100, + }); + const existingComment = comments.find( + (comment) => comment.user?.type === "Bot" && comment.body?.includes(marker) + ); + + if (existingComment) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existingComment.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } + + - name: Fail on missing routing + if: steps.routing-check.outcome == 'failure' + run: exit 1 diff --git a/AGENTS.md b/AGENTS.md index e5d87ea..43b9676 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -200,8 +200,9 @@ Follow the full guide in `CONTRIBUTING.md`. In short: 2. Create `plugins/expo/skills//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. +5. Add a one-line entry for the skill to the `expo-overview` Skill Map (`plugins/expo/skills/expo-overview/SKILL.md`) so the router can dispatch to it. This is enforced by the `skill routing coverage` workflow (`bun scripts/check-overview-routing.ts`). +6. Bump the version in all three plugin manifests together (they must match and be greater than main; CI-enforced). +7. Keep the skill under the existing `expo` plugin unless there is a clear distribution reason to create a new plugin. ## Testing Plugins diff --git a/README.md b/README.md index 094a990..55aafac 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,14 @@ Agents choose the right skill from the task context and each skill's description Skills come in two groups so the free vs paid boundary is clear. Each skill's description carries the same label, and every services skill opens with a costs/plan-limits note. +### Start Here + +Load this first for any Expo/EAS task, then route to the specific skill below. + +| Skill | Use it for | +| --- | --- | +| `expo-overview` | Router and shared setup rules; the entry point when a request is vague or the user hasn't named a specific Expo tool. | + ### Framework (open source) Free, open-source Expo SDK and React Native skills. diff --git a/plugins/expo/README.md b/plugins/expo/README.md index 9b59696..1893037 100644 --- a/plugins/expo/README.md +++ b/plugins/expo/README.md @@ -60,6 +60,10 @@ Skills come in two groups so the free vs paid boundary stays clear: open-source ## Skills Included +### Start Here + +- **expo-overview** — Router and shared setup rules; the entry point when a request is vague or the user hasn't named a specific Expo tool + ### Framework (open source) - **expo-router** - Navigation and routing: file-based routes, links, native stacks, modals, sheets, native tabs, and headers diff --git a/plugins/expo/skills/README.md b/plugins/expo/skills/README.md index 0c771dd..e877e57 100644 --- a/plugins/expo/skills/README.md +++ b/plugins/expo/skills/README.md @@ -2,6 +2,14 @@ Every skill in this directory is a sibling folder containing a `SKILL.md`. Skills are discovered **one level deep** - `skills//SKILL.md` - so this index groups them for humans; the filesystem stays flat. Each skill's frontmatter `description` is prefixed with its category so agents can see the free vs paid boundary in the always-loaded metadata. +## Start Here + +Load this first for any Expo/EAS task, then route to the specific skill below. + +| Skill | Use it for | +| --- | --- | +| `expo-overview` | Router and shared setup rules; the entry point when a request is vague or the user hasn't named a specific Expo tool. | + ## Framework (open source) Free, open-source Expo SDK and React Native skills. Descriptions are prefixed `Framework (OSS).`. diff --git a/plugins/expo/skills/expo-native-ui/SKILL.md b/plugins/expo/skills/expo-native-ui/SKILL.md index 4b1024a..7f1281e 100644 --- a/plugins/expo/skills/expo-native-ui/SKILL.md +++ b/plugins/expo/skills/expo-native-ui/SKILL.md @@ -9,6 +9,8 @@ license: MIT For routes, links, stacks, tabs, modals, sheets, and headers, use the `expo-router` skill. +> **Before picking any UI component, check `expo-ui` first.** `@expo/ui` provides native equivalents — BottomSheet, Button, Picker, Slider, Menu, Section, Switch, SegmentedControl, and more — rendered as real SwiftUI on iOS and Jetpack Compose on Android, available in Expo Go on SDK 56+ with no custom build. Load the **`expo-ui`** skill to find the right component before falling back to React Native built-ins or community libraries. This skill (`building-native-ui`) covers the surrounding structure: Expo Router navigation, layout, styling, and animations. + ## References Consult these resources as needed: @@ -65,6 +67,7 @@ Expo Go supports a huge range of features out of the box: ## Library Preferences +- **For any sheet, picker, slider, toggle, menu, or grouped-form section: use `@expo/ui` (see `expo-ui` skill) before reaching for a React Native built-in or community library** — it renders native SwiftUI/Compose and works in Expo Go on SDK 56+. For grouped/settings-style rows (short, fixed-length), use `@expo/ui`'s `List` + `ListItem`. For large or unknown-length scrolling lists (feeds, search results, catalogs), use `FlatList` or `FlashList` — `@expo/ui`'s `List` is not virtualized. - Never use modules removed from React Native such as Picker, WebView, SafeAreaView, or AsyncStorage - Never use legacy expo-permissions - `expo-audio` not `expo-av` diff --git a/plugins/expo/skills/expo-native-ui/references/controls.md b/plugins/expo/skills/expo-native-ui/references/controls.md index 762fe20..0b0257d 100644 --- a/plugins/expo/skills/expo-native-ui/references/controls.md +++ b/plugins/expo/skills/expo-native-ui/references/controls.md @@ -2,6 +2,8 @@ Native iOS controls provide built-in haptics, accessibility, and platform-appropriate styling. +> **Prefer `@expo/ui` for these controls.** Its `Slider`, `Switch`, `DateTimePicker`, `Menu`, and segmented pickers render as real SwiftUI / Jetpack Compose and follow platform design conventions automatically — recommended over the React Native controls below. See the **`expo-ui`** skill; use the React Native controls below as a fallback when you're not using `@expo/ui`. + ## Switch Use for binary on/off settings. Has built-in haptics. diff --git a/plugins/expo/skills/expo-overview/SKILL.md b/plugins/expo/skills/expo-overview/SKILL.md new file mode 100644 index 0000000..424ad86 --- /dev/null +++ b/plugins/expo/skills/expo-overview/SKILL.md @@ -0,0 +1,85 @@ +--- +name: expo-overview +description: "Framework (OSS). Entry point and router for any Expo or EAS task — load this skill first, before writing any code, whenever the task involves building, extending, or modifying a React Native / Expo app. Triggers on: any multi-screen app spec or design to implement (tabs, stacks, maps, lists, navigation); building from a screenshot or design reference; adding screens or features to an existing Expo project; or when the request mentions Expo, EAS, React Native, or any expo-* package. Handles phrasings like 'implement a mobile app', 'build an app from this screenshot', 'make my app look native', 'build a beautiful app', 'align with iOS / SwiftUI best practices', 'add navigation', 'fetch some data', 'upgrade my SDK', 'add Expo to my existing native app', 'ship to the App Store', or 'I'm new to Expo, where do I start'. Detects the real goal, routes to the right expo-* / eas-* skill, and owns the shared environment and setup rules the other Expo skills rely on." +version: 1.0.0 +license: MIT +--- + +# `expo-overview` — router & shared rules for Expo / EAS + +## Start Here — read before doing anything + +**Do not guess the skill from project files alone.** Many Expo goals look similar from +the filesystem but need different skills. + +1. **Read the user's goal** — what outcome do they want, in plain terms? +2. **Classify it** using the Skill Map below, translating casual phrasing to a goal. +3. **Confirm intent** if ambiguous ("Sounds like you want to ship to the stores — that's + `eas-app-stores`. Right?"), then load that skill's `SKILL.md` and follow it. +4. **Trust the leaf skill** — it has its own detection logic and steps. Don't improvise. + +## Skill Map (by goal) + +Match the goal to a category, then the skill, then load that leaf's `SKILL.md`. + +**Build the app** +- `expo-native-ui` — screens, styling, semantic colors, native controls, SF Symbols, media, animations, layout +- `expo-router` — navigation: file-based routes, tabs / stacks / modals / sheets, links, headers +- `expo-ui` — native UI components via `@expo/ui`: BottomSheet, Picker, Slider, Button, Menu, Section, and more — real SwiftUI on iOS, Jetpack Compose on Android, available in Expo Go on SDK 56+; also drop-in replacements for `@gorhom/bottom-sheet`, `datetimepicker`, etc. +- `expo-tailwind-setup` — Tailwind / NativeWind styling +- `expo-data-fetching` — network requests, React Query / SWR, caching, offline, route loaders +- `expo-dom` — run web code or reuse a web library inside native +- `expo-web-to-native` — migrate an existing web / React app to a native iOS / Android app + +> **Component selection rule:** whenever you need a UI component (list rows, bottom sheets, pickers, sliders, menus, buttons, segmented controls, toggles), **consult `expo-ui` first** to check whether `@expo/ui` has a native equivalent before reaching for a React Native built-in or a community library. Native `@expo/ui` components give the best platform fit with zero extra install steps on SDK 56+. Load `expo-ui` alongside `expo-native-ui` for any app that renders lists, detail sheets, or form controls. + +**Ship & operate** +- `eas-app-stores` — build and submit to the App Store / Play Store / TestFlight, versions, and store metadata +- `eas-hosting` — deploy the web bundle and Expo Router API routes to EAS Hosting +- `eas-workflows` — EAS Workflow YAML and CI/CD pipelines +- `eas-simulator` — run and drive the app on a remote iOS / Android simulator on EAS cloud +- `expo-dev-client` — custom development builds +- `eas-update-insights` — OTA update health: crash rate, adoption, payload size +- `eas-observe` — startup / launch / TTI performance with EAS Observe + +**Extend natively** +- `expo-module` — native modules and views (Swift / Kotlin) with the Expo Modules API +- `expo-brownfield` — embed Expo / React Native in an existing native app +- `expo-app-clip` — iOS App Clip target (AASA, smart app banner) + +**Maintain & learn** +- `expo-upgrade` — upgrade the Expo SDK and fix dependency conflicts +- `expo-examples` — canonical, version-matched integration examples (Stripe, Clerk, Supabase, …) + +### Translating vague asks + +Some everyday phrasings don't obviously map to a skill name — translate before routing: + +- "Make it look native" → grouped controls / settings forms = `expo-ui`; screens, styling, animations = `expo-native-ui`; navigation = `expo-router`. +- "Ship it" / "get an .ipa or .apk" / "release to the stores" → `eas-app-stores` (build + submit, TestFlight, versions, store metadata). +- "I'm new / where do I start" → scaffold first (see First Run), then route by goal. + +## First Run / shared rules + +These apply across every Expo skill, so handle them here once instead of repeating them +in each leaf. + +- **No Expo project yet?** Start one the standard way before routing to a feature skill: + `npx create-expo-app@latest`. Then classify the user's goal and route. +- **Detect the SDK version** before giving version-specific advice: read the `expo` + version in `package.json` (and `app.json` / `app.config.{js,ts}`). Many APIs and + defaults differ by SDK. +- **Managed vs. bare/prebuild**: the presence of committed `ios/` and `android/` + directories means native projects exist (prebuild or bare). Config-plugin and + native-setup steps differ — note which one the project is in. +- **Install packages with `npx expo install `**, not raw `npm`/`yarn`/`pnpm add`, + so versions stay compatible with the project's SDK. +- **EAS auth & linking** (only needed for build/submit/update/observe/workflows): check + login with `eas whoami`, log in with `eas login`. A project is linked when + `extra.eas.projectId` exists in the app config; create it with `eas init` if missing. + +## When NOT to use this skill + +- The user already named a specific Expo workflow or tool → go straight to that skill. +- A more specific `expo-*` / `eas-*` skill obviously fits the request → use it and skip + the router hop. diff --git a/plugins/expo/skills/expo-overview/agents/openai.yaml b/plugins/expo/skills/expo-overview/agents/openai.yaml new file mode 100644 index 0000000..1c6e725 --- /dev/null +++ b/plugins/expo/skills/expo-overview/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Expo Overview" + short_description: "Entry point and router for Expo / EAS tasks: classify the goal, route to the right expo-* / eas-* skill, and apply shared setup rules" + default_prompt: "Use $expo-overview first for any Expo or EAS task — classify the user's goal, route to the right expo-* / eas-* skill, and apply the shared environment and setup rules the other Expo skills rely on." diff --git a/plugins/expo/skills/expo-ui/SKILL.md b/plugins/expo/skills/expo-ui/SKILL.md index 2c14a82..4fbd062 100644 --- a/plugins/expo/skills/expo-ui/SKILL.md +++ b/plugins/expo/skills/expo-ui/SKILL.md @@ -1,6 +1,6 @@ --- name: expo-ui -description: "Framework (OSS). Build native UI with the @expo/ui package: real SwiftUI on iOS and Jetpack Compose on Android rendered from React in an Expo or React Native app. Covers universal cross-platform components (Host, Column, Row, Button, Text, List, and more imported from @expo/ui), drop-in replacements for popular React Native community libraries (BottomSheet, DateTimePicker, Slider, Menu, etc.), and platform-specific SwiftUI (@expo/ui/swift-ui, iOS only) and Jetpack Compose (@expo/ui/jetpack-compose, Android only) trees and modifiers. Use when adding or reviewing @expo/ui Host/RNHostView trees, building native-feeling UI where standard React Native components fall short (grouped settings forms with toggles, sections, menus, sheets, pickers, sliders), choosing between universal and platform-specific components, or replacing an RN community UI library with a native @expo/ui equivalent. Not for custom native modules, Expo Router navigation, Reanimated, or data fetching." +description: "Framework (OSS). Build native UI with the @expo/ui package: real SwiftUI on iOS and Jetpack Compose on Android. Default to @expo/ui for sheets (BottomSheet), pickers, sliders, toggles, menus, and grouped-form sections — do NOT reach for Reanimated, @gorhom/bottom-sheet, or RN built-in Picker/Switch; use @expo/ui instead. Fall back to RN built-ins only when @expo/ui is missing the component. NOTE: @expo/ui List renders native grouped rows like an iOS Settings screen — it is NOT a virtualized list; use FlatList/FlashList for large datasets. Covers universal components (Host, Column, Row, Button, Text, List, BottomSheet, FieldGroup, Switch, Slider, Picker, Menu), drop-in replacements for RN community libraries, and platform-specific SwiftUI/Jetpack Compose trees. Not for Expo Router navigation, Reanimated, or data fetching." version: 1.0.0 license: MIT allowed-tools: "Bash(node *expo-ui/scripts/list-components.js *)" @@ -8,9 +8,9 @@ allowed-tools: "Bash(node *expo-ui/scripts/list-components.js *)" # Expo UI (`@expo/ui`) -`@expo/ui` renders real native UI from React: SwiftUI on iOS, Jetpack Compose on Android. Start with its universal components (one tree for iOS, Android, and web) and drop to platform-specific SwiftUI/Jetpack Compose only when the universal layer falls short. It also ships drop-in replacements for migrating off RN community UI libraries. +`@expo/ui` renders real native UI from React: SwiftUI on iOS, Jetpack Compose on Android. It also ships drop-in replacements for migrating off RN community UI libraries. -> These instructions track the latest Expo SDK. The **universal** layer requires **SDK 56+**. Drop-in replacements and the platform-specific layers also exist on SDK 55. For component details on a specific SDK, refer to the Expo UI docs for that version. +> These instructions track the latest Expo SDK. The **universal** layer requires **SDK 56+** and works in Expo Go — no custom build needed. Drop-in replacements and the platform-specific layers also exist on SDK 55. For component details on a specific SDK, refer to the Expo UI docs for that version. ## Installation @@ -18,11 +18,57 @@ allowed-tools: "Bash(node *expo-ui/scripts/list-components.js *)" npx expo install @expo/ui ``` -On SDK 56, `@expo/ui` works in Expo Go, so `npx expo start` runs it directly — no custom build required. On older SDKs, build a dev client first (`npx expo run:ios` / `npx expo run:android`). - Every `@expo/ui` tree — universal or platform-specific — must be wrapped in `Host`. -## Choosing an approach (read this first) +## Use @expo/ui by default — don't reach for RN alternatives first + +**Before using Reanimated, `@gorhom/bottom-sheet`, React Native's built-in `Switch`/`Picker`, or any community UI library for the items below, use `@expo/ui` instead.** Only fall back to RN built-ins when `@expo/ui` is missing the component. + +| Need | Use | +|------|-----| +| Slide-up sheet / bottom sheet | `BottomSheet` from `@expo/ui` — **not** Reanimated or `@gorhom/bottom-sheet` | +| Grouped native list rows (settings/form-style) | `List` + `ListItem` from `@expo/ui` — **not** `FlatList` (see note below) | +| Toggle | `Switch` from `@expo/ui` | +| Slider | `Slider` from `@expo/ui` | +| Date/time picker | `@expo/ui/community/datetimepicker` | +| Menu | `Menu` from `@expo/ui` | +| Form section with label | `FieldGroup` from `@expo/ui` | +| Collapsible section | `Collapsible` from `@expo/ui` | + +> **`List` is NOT a virtualized scrolling list.** It renders native grouped table rows — the visual look of an iOS Settings screen or a form section, with disclosure indicators and native row styling. Each `ListItem` is a native node on the JS thread; rows are not recycled. For any list with large or unknown-length data (feeds, search results, catalogs), use **`FlatList`** or **`FlashList`** instead. `List` is the right choice for short, fixed-length groups: a settings screen, a detail panel's rows, a fixed menu. + +**`BottomSheet` example** (use this for map pin details, action sheets, detail panels — not Reanimated): + +```tsx +import { Host, BottomSheet, Column, Text } from '@expo/ui'; +import { useState } from 'react'; + +export default function MapScreen() { + const [isOpen, setIsOpen] = useState(false); + + return ( + + setIsOpen(true)} /> + + setIsOpen(false)} + snapPoints={['half', 'full']} + > + + Café name + Address + + + + + ); +} +``` + +`BottomSheet` uses `isPresented`/`onDismiss` — **not** `isOpened`, `isOpen`, `onIsOpenedChange`, or `onChange` (those are `@gorhom/bottom-sheet` props and will silently do nothing). `snapPoints` accepts `'half'`, `'full'`, `{ fraction: 0.5 }`, or `{ height: 400 }` and is optional (auto-sizes to content when omitted). + +## Choosing an approach Work down this list and stop at the first layer that meets the need: diff --git a/plugins/expo/skills/expo-ui/references/universal.md b/plugins/expo/skills/expo-ui/references/universal.md index fb6170a..e71931c 100644 --- a/plugins/expo/skills/expo-ui/references/universal.md +++ b/plugins/expo/skills/expo-ui/references/universal.md @@ -61,6 +61,33 @@ export default function MyInput() { Docs — https://docs.expo.dev/versions/latest/sdk/ui/universal/textinput/index.md +## BottomSheet + +`BottomSheet` is a universal slide-up sheet. Use `isOpened` / `onIsOpenedChange` to control open state — **not** `isPresented`, `onDismiss`, or `snapPoints` (those are `@gorhom/bottom-sheet` props; the universal `BottomSheet` does not accept them). + +```tsx +import { Host, BottomSheet, Column, Button, Text } from '@expo/ui'; +import { useState } from 'react'; + +export default function MapScreen() { + const [isOpen, setIsOpen] = useState(false); + + return ( + + + + + Sheet content + + + + + ); +} +``` + +> **Don't confuse the universal `BottomSheet` with the drop-in `@gorhom/bottom-sheet` replacement** (`@expo/ui/community/bottom-sheet`). They are different components with different APIs. Use the universal one when building new UI; use the drop-in only when migrating an existing `@gorhom/bottom-sheet` integration. See `./drop-in-replacements.md`. + ## Confirming the API `@expo/ui` is versioned with the Expo SDK (e.g. `56.0.x` for SDK 56) and its API can change between SDK versions, so the **installed package's TypeScript types (`.d.ts`) are the most reliable source of truth** — they match the version in your project, while the docs track latest. Read the relevant component's `.d.ts` from the installed `@expo/ui` package in `node_modules`. Use the docs as the human-readable reference: diff --git a/scripts/check-overview-routing.ts b/scripts/check-overview-routing.ts new file mode 100644 index 0000000..5ccb18c --- /dev/null +++ b/scripts/check-overview-routing.ts @@ -0,0 +1,59 @@ +#!/usr/bin/env bun + +import { mkdirSync, readFileSync, readdirSync, writeFileSync, existsSync } from "node:fs"; +import { dirname, join } from "node:path"; + +const SKILLS_DIR = "plugins/expo/skills"; +const ROUTER_SKILL = "expo-overview"; +const ROUTER_PATH = join(SKILLS_DIR, ROUTER_SKILL, "SKILL.md"); + +function listSkillNames(): string[] { + return readdirSync(SKILLS_DIR, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .filter((name) => name !== ROUTER_SKILL) + .filter((name) => existsSync(join(SKILLS_DIR, name, "SKILL.md"))) + .sort(); +} + +function writeSummary(markdown: string) { + const summaryPath = process.env.ROUTING_CHECK_SUMMARY_PATH; + if (!summaryPath) { + return; + } + + mkdirSync(dirname(summaryPath), { recursive: true }); + writeFileSync(summaryPath, `${markdown}\n`); +} + +function complete(success: boolean, markdown: string): never { + writeSummary(markdown); + console.log(markdown); + process.exit(success ? 0 : 1); +} + +const skillNames = listSkillNames(); +const router = readFileSync(ROUTER_PATH, "utf8"); + +// A skill is "routed" when it appears as an inline-code token: `skill-name`. +const missing = skillNames.filter((name) => !router.includes(`\`${name}\``)); + +const markdown = [ + "## Expo overview routing check", + "", + missing.length === 0 + ? `Passed. All ${skillNames.length} skills are referenced in \`${ROUTER_PATH}\`.` + : "Failed. New skills must be added to the `expo-overview` Skill Map so the router can dispatch to them.", + ...(missing.length === 0 + ? [] + : [ + "", + "Skills missing from the router:", + "", + ...missing.map((name) => `- \`${name}\``), + "", + `Add each one as an inline-code entry (\`\`${"skill-name"}\`\`) under a category in \`${ROUTER_PATH}\`.`, + ]), +].join("\n"); + +complete(missing.length === 0, markdown); diff --git a/skills.sh.json b/skills.sh.json index 7a41625..5b6a9f7 100644 --- a/skills.sh.json +++ b/skills.sh.json @@ -6,6 +6,7 @@ "title": "Framework (open source)", "description": "Free, open-source Expo SDK and React Native skills: UI, navigation, styling, data fetching, native modules, DOM, examples, and SDK upgrades.", "skills": [ + "expo-overview", "expo-router", "expo-native-ui", "expo-ui",