用途
该技能用于让 Expo 应用保持一种可证明的架构形态:每个文件在 src/ 布局中都有唯一归属,依赖沿声明的区域单向流动,src/core 保持纯净,每个副作用都位于带适配器和内存假实现的端口之后,由唯一的组合根创建适配器,且导入时不执行任何逻辑。两个零依赖检查器会在每次变更时基于解析后的导入进行验证,iOS 与 Android 一致。
主要能力
技能定义了一组必须成立的规则,覆盖文件放置、路由、依赖方向、核心纯净性、端口与适配器、组合根、导入时行为、Node 世界边界、导入解析、require 用法、入口与别名、多包工作区等。
- 每个文件只有一个归属:src/app(仅路由)、src/features/<feature>/、src/ui、src/theme、src/core、src/services/<concern>/、src/stores、src/data、src/api、src/i18n、src/config、src/hooks、src/canvas、src/games/<game>/ 等;资源放在根目录 assets/<kind>/,本地 Expo 模块放在 modules/<name>/。
- 路由是薄叶子:src/app 中每个文件默认导出一个路由,渲染来自 src/features/ 的屏幕(或仅重定向);除 _layout 外的路由不得导入 store、src/data、src/api 或服务,也不得调用除 router 钩子及从 src/features/ 导入的钩子之外的钩子;除测试外任何文件不得导入路由。
- 依赖单向流动:routes → features → 共享层(ui、theme、services、stores、data、api、i18n、config)→ core。共享层不得导入 feature,feature 之间不得互相导入,src/ui 为展示型。游戏不得导入其他游戏,只面向 src/core、src/ui、src/theme、src/i18n、src/canvas、端口类型、自身文件夹及 gameFacing 中记录的接缝。
- src/core 保持纯净:不得使用 React、React Native、Expo、Skia、Zustand、导航或 i18n 库,不得使用 Node 内置模块,不得引用 src/core 之外的内容;时间和随机性以值的形式传入。
- 每个副作用位于端口之后,每个 SDK 都被分类:在 src/services/<concern>/<concern>-port.ts 中定义厂商中立的类型;SDK 的唯一导入者是 <library>-<concern>-adapter.ts;提供带自身测试的内存假实现 fake-<concern>.ts。应用运行时导入的每个 SDK 要么在 vendorSdks(位于端口之后),要么在 portlessSdks(UI 构建块和只读事实,附原因)。
- 唯一组合根创建适配器:createDeviceServices() 只构建每个适配器一次;只有根布局导入它、在 useState 初始化器中调用并渲染 ServicesProvider;代码通过 useServices() 读取端口或作为工厂参数接收;只有组合根导入适配器。测试通过 createTestServices() 传入假实现,绝不对应用自身模块使用 jest.mock()。
- 导入时不执行任何逻辑:不得在模块级打开数据库、创建适配器或服务、创建 store、AudioContext、切换方向或重载。唯一的模块级 store 是 OS 镜像。
- 应用代码不得导入 Node 世界代码;Node 代码只加载中立文件。Expo Router 服务端代码(src/app 下每个 +api 文件、根 +middleware.ts、契约 serverFiles)在服务端运行,不得进入原生包。
- 导入必须可解析且不成环:Node 代码之外不得使用 ../;不得有解析为空的导入;值导入之间不得成环。
- require() 仅用于资源和 test-only 门:资源通过其文件 require;test-only 代码只有一条入口,即 src/**/test-only.ts 门。
- 入口保持薄且别名存在:package.json main 为 expo-router/entry;tsconfig.json 将 @/* 映射到 src/*;src/app/_layout.tsx 存在。文件夹别名需在根 tsconfig.json、jest.config.js moduleNameMapper 和 src/tsconfig.json 三处配置。
- 多包时,apps 之间不得互相导入,packages 不得导入 app。
- 更改架构前需停下并询问:新增顶层文件夹、包、SDK 分类、禁令、gameFacing 接缝或例外属于开发者决策,需记录在 contracts/architecture.json 和 eslint.config.mjs 中。
使用方式
技能提供工作流:在写代码前先命名任务并阅读对应参考文档。
- 文件放置、新文件夹、屏幕、feature、API 路由、本地 Expo 模块或新文件类型:阅读 references/src-layout-and-placement.md
- 跨文件夹导入、边界 lint 错误、新 SDK、游戏导入、Node 代码导入应用代码、服务端代码、资源别名、test-only 门:阅读 references/dependency-zones.md
- 新增或修改服务、适配器、假实现、组合根、使用假实现测试:阅读 references/ports-and-adapters.md
- contracts/architecture.json、rn-skills.config.json、例外、移动文件夹:阅读 references/contract-and-config.md
- monorepo(apps 和 packages):阅读 references/multi-package.md
新应用或首个端口:将 templates/src/services/(组合根;clock、error-log 和 direction-guard 端口)复制到 src/services/,将 templates/contracts/ 复制到 contracts/。端口导入 src/core/result.ts、src/core/dates/date-key.ts 和 src/i18n/languages.ts:仅当应用尚无这些文件时从 templates/src/ 复制。按 examples/root-layout.tsx 所示挂载根。
新增端口:按 ports-and-adapters.md 中的 add-a-port 配方操作:将 templates/port/ 的四个文件复制到 src/services/<concern>/(填写每个 PLACEHOLDER;clock 文件夹是完成示例),先写假实现及其测试,再写适配器;将端口加入 Services、createDeviceServices 和 createTestServices;将 SDK 加入 contracts/architecture.json 的 vendorSdks 和 eslint.config.mjs 的 VENDOR_SDK_PATHS。模型钩子随后按 examples/use-tasks-model.ts 读取它。
新增 SDK 或文件:在首次导入前对每个新 SDK 分类;按放置表放置每个文件;首次 @/assets/(或 @/modules/)导入需在三处添加别名。让 Prettier 格式化填充后的模板。
在新增顶层文件夹、工作区包、SDK 分类、禁令、gameFacing 接缝或 allowedImports 例外前,停下并询问开发者,然后将决策记录在 contracts/architecture.json。
检查(验证循环):当 node_modules 存在时运行 npm run lint、npm run typecheck 和 npm test;新增 SDK 后还需运行 npx expo install --check、npx expo-doctor 和 npm ls react react-native。始终从仓库根运行 node ${CLAUDE_SKILL_DIR}/scripts/check-layout.mjs . 和 node ${CLAUDE_SKILL_DIR}/scripts/check-boundaries.mjs .。修复每个 FAIL 行并重跑,直到两者都打印 RESULT: PASS。
适用场景
适用于 Expo/React Native 应用在新增文件、新增文件夹、新增屏幕、新增 feature、新增 API 路由、新增本地 Expo 模块、新增文件类型、跨文件夹导入、处理边界 lint 错误、新增 SDK、处理游戏导入、Node 代码导入应用代码、服务端代码、资源别名、test-only 门、新增或修改服务/适配器/假实现/组合根、使用假实现测试、修改 contracts/architecture.json、处理例外、移动文件夹、搭建 monorepo 等场景。
限制
技能要求两个检查器均打印 RESULT: PASS,且不打印 SKIP 行。没有 contracts/architecture.json 时,每个检查器打印一行 note 并应用默认值;多包仓库需要该契约。在没有应用依赖 expo 的仓库中,check-layout 打印 NOT APPLICABLE: expo is not a dependency in package.json(视为通过)。
反模式:src/ 下的 utils/、helpers/ 或 components/ 文件夹。纯逻辑应放入 src/core 或其 feature,共享 UI 放入 src/ui,钩子放在使用它的 feature 旁边(仅当两个 feature 共享时才放入 src/hooks/)。
关于收费、价格、平台支持范围等更多信息,资料中暂未提供。