Skip to content
Back to skills

Architecture And Boundaries

ASecurity

Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root. Use when adding a file, folder, port or adapter, or on an import error. Not for names (naming-conventions).

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 18, 2026
developmentjavascripttypescriptgojavasqlreactnodetestingapidatabase

Works with

  • api

Security analysis

A100/100

Pro scans all 19 files and shows the line behind each finding

Scanned October 18, 2026

npx -y skills add zakariaf/ReactNative-Skills --skill architecture-and-boundaries --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Architecture And Boundaries?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Architecture And Boundaries
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zakariaf-architecture-and-boundaries/badge)](https://www.skillsdirectory.com/skills/zakariaf-architecture-and-boundaries)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: architecture-and-boundaries
description: "Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root. Use when adding a file, folder, port or adapter, or on an import error. Not for names (naming-conventions)."
---

# Architecture and boundaries

Keeps an Expo app in a provable shape: every file has one home in the `src/` layout, dependencies point
one way along declared zones, `src/core` is pure, every side effect sits behind a port with an adapter
and an in-memory fake, one composition root creates the adapters, and nothing runs at import time. Two
zero-dependency checkers prove it on every change, on resolved imports, for iOS and Android alike.

## Rules that must hold

1. **Every file has one home.** `src/app` (routes only), `src/features/<feature>/` (screen, model hook,
   components, pure logic, tests beside them), `src/ui`, `src/theme`, `src/core`, `src/services/<concern>/`,
   `src/stores`, `src/data`, `src/api`, `src/i18n`, `src/config`, `src/hooks` (shared cross-feature hooks),
   `src/canvas` (the canvas kit), and `src/games/<game>/` for the games plugin; a `src/**/test-only.ts` gate beside its entry; a web stylesheet in `src/theme/`;
   `assets/<kind>/` at the root for images, fonts and sounds; local Expo modules in `modules/<name>/`;
   `test/`, `__mocks__/`, `scripts/<area>/`, `plugins/`, `contracts/`. Why: an agent finds code by path, and
   a `utils/` or `helpers/` folder becomes the place everything goes. (`folders`, `placement`)
2. **Routes are thin leaves.** Every file in `src/app` default-exports a route (`export default`, or
   `export { SettingsScreen as default } from ...`) that renders a screen from `src/features/` (or only
   redirects); hooks and helpers live elsewhere, and tests in the feature (a route test is
   `src/features/<feature>/<feature>-navigation.test.tsx`); a route other than a `_layout` never imports a
   store, `src/data`, `src/api` or a service and calls no hook but router hooks (`expo-router`) and the ones
   it imports from `src/features/` (no `useState`, `useEffect`, `useQuery`, `useMemo`, shared or local
   hooks); nothing but a test imports a route. Why: Expo Router bundles every file in that folder (verified:
   a test file there shipped in the production bundle), and logic in a route can be neither reused nor
   tested alone. (`route-file`, `route-thin`, `route-leaf`)
3. **Dependencies point one way.** routes → features → shared layers (`ui`, `theme`, `services`,
   `stores`, `data`, `api`, `i18n`, `config`) → `core`. A shared layer never imports a feature, features
   never import each other, and `src/ui` is presentational (no stores, data, api, or services other than
   port types). A game never imports another game and faces only `src/core`, `src/ui`, `src/theme`,
   `src/i18n`, `src/canvas`, port types, its own folder and the seam recorded in `gameFacing`. A deliberate exception is an
   `allowedImports` entry with its reason. Why: a feature or a game can change or disappear without breaking
   another, and UI stays reusable with props. (`layer-direction`, `feature-to-feature`, `ui-presentational`,
   `game-to-game`, `game-facing`)
4. **`src/core` is pure.** No React, React Native, Expo, Skia, Zustand, navigation or i18n library, no Node
   built-in, nothing outside `src/core` (game rules and levels: `src/core` plus their own rules and levels).
   Time and randomness arrive as values. Why: pure code runs the same in Jest, Node tooling and on every
   device, and is the cheapest code to test. (`core-pure`)
5. **Every side effect sits behind a port, and every SDK is classified.** A vendor-neutral type in
   `src/services/<concern>/<concern>-port.ts` (expected failures are `Result` values, never rejections);
   the only importer of the SDK is `<library>-<concern>-adapter.ts` (platform files `*-adapter.ios.ts` and
   `*-adapter.android.ts` count, and so do `*-adapter.ts` files in `src/data/`; subpaths such as
   `expo-sqlite/kv-store` are the SDK); an in-memory `fake-<concern>.ts` with its own test. Every SDK the
   app's runtime code imports is either in `vendorSdks` (behind a port) or in `portlessSdks` (UI building
   blocks and read-only facts, with the reason): every `expo-*`, `react-native-*`, `*-react-native(-*)`,
   `@react-native-*/*` and `@scope/react-native*` or `@scope/*-react-native*` package, any name in the
   contract's `sdkPackagePatterns`, and (when `node_modules` exists) any package that ships native code. A
   local Expo module (`modules/<name>/`) is an SDK too: only its adapter imports it. Pure logic gets no port.
   Why: Jest never touches native code, a vendor swap touches one file, and a new SDK forces a decision
   instead of slipping into a screen. (`vendor-sdk`, `unclassified-sdk`, `port-triple`, `banned-package`)
6. **One composition root creates the adapters; fakes stay in tests.** `createDeviceServices()` builds each
   adapter once; only the root layout imports it, calls it in a `useState` initializer and renders
   `ServicesProvider`; code reads the ports with `useServices()` or receives them as factory arguments; only
   the composition root imports an adapter (the data layer's device opener `src/data/open-device-*.ts` and
   app-startup-and-bootstrap's root-kit registry `src/services/create-root-kits.ts` are part of it). Tests pass fakes through `createTestServices()`, never `jest.mock()` of the app's own
   modules; runtime code never imports a fake or `createTestServices` (the test-only entry may). Context
   carries dependencies, never state that changes. Why: one file lists every outside dependency, a render
   never rebuilds the device, and a test controls exactly the ports it touches. (`adapter-import`,
   `composition-root`, `fake-import`)
7. **Nothing runs at import time.** No module-level database open, adapter or services creation, store
   (a zustand `create`, a vanilla `createStore`, a project factory such as `createTasksStore()`),
   `AudioContext`, direction switch or reload. The only module-level store is an OS mirror: a vanilla store
   with a plain initial state, fed by a platform listener. Why: a reload, a direction change and Fast Refresh re-run
   modules on iOS and Android, so module-level work runs twice and tests share state. (`import-time-work`)
8. **App code never imports Node-world code; Node code loads only neutral files.** `app.config.ts`,
   `plugins/` and `scripts/` run in Node: app code never imports them (not even `import type`) or a Node
   built-in (an npm polyfill the app declares, such as `buffer`, is a package). A `src/` file that Node code
   imports stays neutral: no JSX, no React Native or Expo import, relative imports with the extension.
   Expo Router server code (every `+api` file under `src/app`, the root `+middleware.ts`, contract
   `serverFiles` such as `src/server/**`) runs on the server, never in the native bundle: it may use Node
   built-ins, imports only `src/core`, `src/api`, `src/config`, `src/data`, port types, `expo-server` and
   `expo-router/server`, and app code never imports it. Why: a type-only import drags `process` and
   `node:fs` into the app program, Node ignores tsconfig paths and cannot load Expo modules (verified:
   `expo config` fails), and server code runs where React Native and the device do not exist (the native
   bundle leaves `+api` and `+middleware` out, verified in expo-router 57.0.24).
   (`node-world-import`, `node-builtin`, `node-import`, `no-tooling-import`, `server-boundary`)
9. **Imports resolve and never loop.** No `../` outside Node code (use `./sibling`, `@/` or `@/assets/`), no
   import that resolves to nothing, no cycle among value imports (type-only imports are erased and do not
   count). Why: parent paths hide which zone a file reaches, and a cycle makes a value `undefined` depending
   on which file loads first. (`parent-import`, `unresolved-import`, `import-cycle`)
10. **`require()` is for assets and the test-only gate.** An asset is required by its file
    (`require('@/assets/images/empty-list.png')`, through the alias of rule 11; an extension the app adds
    to Metro's `resolver.assetExts` goes into the contract's `assetExtensions`). Test-only code has one way
    in: a `src/**/test-only.ts` gate beside its entry,
    `process.env.EXPO_PUBLIC_APP_VARIANT === 'production' ? null : require('./test-only-entry')`; nothing
    else imports the entry, code the entry reaches never imports the gate, and other bundled code reaches a
    `testOnlyFolders` folder only through `TEST_ONLY` (a debug route renders `TEST_ONLY?.DebugScreen`;
    `import type` is fine). The forms app-variants-and-environment sanctions pass too: debug tooling behind
    `__DEV__` (Reactotron, `devOnlyRequires`, anywhere in an `if (__DEV__)` block) and another
    `EXPO_PUBLIC_*` inclusion flag at its `require()`. That skill owns the comparison's literal and polarity. Why: one gate is one place to prove the strip, and a module reached
    any other way ships in production (verified on iOS and Android exports). (`test-only-gate`,
    `import-cycle`)
11. **The entry is thin and the aliases exist.** `package.json` `main` is `expo-router/entry` (or an
    `index.ts` that only imports, ending in `expo-router/entry`); `tsconfig.json` maps `@/*` to `src/*`;
    `src/app/_layout.tsx` exists. A folder alias beside `@/*` lives in three places: the root
    `tsconfig.json` (`"@/assets/*": ["./assets/*"]`, the Expo template default; `"@/modules/*":
    ["./modules/*"]`), `jest.config.js` `moduleNameMapper` (`'^@/assets/(.*)$': '<rootDir>/assets/$1'`
    **before** `'^@/(.*)$'`; with no `@/` mapper of its own, jest-expo maps `tsconfig.json` `paths` in their
    order, so list `"@/assets/*"` before `"@/*"` there), and for imports `src/tsconfig.json`. Why:
    startup work belongs in the root layout and the composition root, where it is ordered and testable;
    every rule above assumes the alias; and Jest takes the first mapper that matches, so without the order
    `npm test` fails ("Could not locate module @/assets/... mapped as <rootDir>/src/$1") while tsc, Metro and both
    checkers pass. (`app-entry`, `app-files`)
12. **When multi-package, apps never import each other and packages never import an app.** Workspaces
    `apps/*` + `packages/*`, each package recorded in the contract (`pure`, `world`), packages consumed as
    TypeScript source, one React, every native module in every app at one version. Why: one change in a
    package reaches every app, and autolinking (iOS and Android) sees only an app's own dependencies.
    (`workspaces`, `manifest`, `one-react`, `native-deps`, `app-to-app`, `package-direction`)
13. **Stop and ask before changing the architecture.** A new top-level folder, package, SDK classification,
    ban, game-facing seam or exception is a decision for the developer: it changes
    `contracts/architecture.json` and the same list in `eslint.config.mjs` (a `Gate-Change:` trailer). Why:
    these choices bind every later change.

## Workflow

1. Name the task and read its reference before writing code:

   | Task | Read |
   |---|---|
   | where a file goes; a new folder, screen, feature, API route, local Expo module or kind of file | [references/src-layout-and-placement.md](references/src-layout-and-placement.md) |
   | an import across folders, a boundary lint error, a new SDK, a game's imports, Node code that imports app code, server code, an asset alias, the test-only gate | [references/dependency-zones.md](references/dependency-zones.md) |
   | a new or changed service, adapter, fake, the composition root, testing with fakes | [references/ports-and-adapters.md](references/ports-and-adapters.md) |
   | `contracts/architecture.json`, `rn-skills.config.json`, an exception, a moved folder | [references/contract-and-config.md](references/contract-and-config.md) |
   | a monorepo (apps and packages) | [references/multi-package.md](references/multi-package.md) |

2. **New app or first port.** Copy `templates/src/services/` (the composition root; the clock, error-log
   and direction-guard ports) to `src/services/`, and `templates/contracts/` to `contracts/`. The ports
   import `src/core/result.ts`, `src/core/dates/date-key.ts` and `src/i18n/languages.ts`: copy them from
   `templates/src/` only when the app does not have them yet (their owners are error-handling-typed-results,
   seeded-determinism-and-golden-vectors and i18n-strings-and-catalogs). Mount the root as
   [examples/root-layout.tsx](examples/root-layout.tsx) shows.
3. **New port.** Follow the add-a-port recipe in ports-and-adapters.md: copy the four files of
   `templates/port/` to `src/services/<concern>/` (fill every `__PLACEHOLDER__`; the clock folder is the
   finished example), write the fake and its test first, then the adapter; add the port to `Services`,
   `createDeviceServices` and `createTestServices`; add the SDK to `vendorSdks` in
   `contracts/architecture.json` and to `VENDOR_SDK_PATHS` in `eslint.config.mjs`. A model hook then reads
   it as [examples/use-tasks-model.ts](examples/use-tasks-model.ts) does.
4. **New SDK or file.** Classify every new SDK (`expo-*`, `react-native-*`, `*-react-native`, a package
   with native code) before the first import: a side effect goes to `vendorSdks` (step 3), a UI building
   block or a read-only fact to `portlessSdks` with its reason. Place every file by the placement table
   (images, fonts and sounds in `assets/<kind>/`, API routes as `src/app/**/<name>+api.ts`, a local Expo
   module in `modules/<name>/` behind an adapter); a new kind of file gets a row. The first `@/assets/` (or
   `@/modules/`) import adds the alias in all three places of rule 11 (the SDK 57 template lists `"@/*"`
   first in `tsconfig.json`; move `"@/assets/*"` above it). Let Prettier format filled templates.
5. **Stop and ask** the developer before a new top-level folder, workspace package, SDK classification,
   ban, `gameFacing` seam or `allowedImports` exception (rule 13), then record the decision in
   `contracts/architecture.json`.
6. **Check (validation loop).** When `node_modules` exist: `npm run lint`, `npm run typecheck` and
   `npm test`; after adding an SDK also `npx expo install --check`, `npx expo-doctor` and
   `npm ls react react-native` (one version of each), which catch SDK version drift. Always, from the repo
   root: `node ${CLAUDE_SKILL_DIR}/scripts/check-layout.mjs .` and
   `node ${CLAUDE_SKILL_DIR}/scripts/check-boundaries.mjs .`. Fix every `FAIL` line (file, rule, fix) and
   rerun until both print `RESULT: PASS`.

| Template | Destination in the app repo |
|---|---|
| `templates/src/services/services-context.tsx`, `create-device-services.ts`, `create-test-services.ts` (+ tests) | `src/services/` (the composition root) |
| `templates/src/services/clock/`, `error-log/`, `direction-guard/` | `src/services/clock/`, `error-log/`, `direction-guard/` as they are |
| `templates/port/port.ts`, `vendor-port-adapter.ts`, `fake-port.ts`, `fake-port.test.ts` | `src/services/<concern>/<concern>-port.ts`, `<library>-<concern>-adapter.ts`, `fake-<concern>.ts`, `fake-<concern>.test.ts` |
| `templates/contracts/architecture.json`, `architecture.schema.json` | `contracts/` |
| `templates/src/core/result.ts`, `templates/src/core/dates/date-key.ts`, `templates/src/i18n/languages.ts` | the same paths, only when missing |

## Definition of done

- [ ] Every new file sits where the placement table says; a new kind of file got a row, and a new
      top-level folder, package, SDK classification, ban, seam or exception was approved and recorded in
      the contract.
- [ ] Route files only render a feature screen (or redirect), calling only router hooks and the feature's; a feature's model
      hook is the code that reads stores and services; `src/ui` receives data through props; `src/core` imports only `src/core`; a game imports
      no other game and only what it faces.
- [ ] Every new outside service has its port, adapter, fake and fake test, is a member of `Services`, is
      created once in `createDeviceServices()` (called by the root layout's `useState` initializer) and stands
      in through `createTestServices()` in tests; every SDK the app imports is in `vendorSdks` or
      `portlessSdks`; no runtime file imports a fake or `createTestServices`.
- [ ] Assets are required from `assets/<kind>/` through `@/assets/`, mapped in `tsconfig.json` and ahead of
      `@/` for Jest; test-only code is required only by a `src/**/test-only.ts` gate (debug tooling behind
      `__DEV__` and other inclusion flags as app-variants-and-environment allows), and a `testOnlyFolders`
      folder is reached only through `TEST_ONLY`.
- [ ] API routes and middleware import no app runtime module or UI; nothing bundled imports them.
- [ ] No module-level side effect; no Node-world import in app code; files Node code imports are neutral.
- [ ] `npm run lint`, `npm run typecheck` and `npm test` pass; after a new SDK, `npx expo install --check`
      and `npx expo-doctor` pass and `npm ls react react-native` shows one version of each.
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-layout.mjs .` prints `RESULT: PASS`.
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-boundaries.mjs .` prints `RESULT: PASS`

Neither checker prints SKIP lines. Without `contracts/architecture.json` each prints one `note:` line and
applies the defaults; a multi-package repo needs the contract. In a repo where no app depends on `expo`,
check-layout prints `NOT APPLICABLE: expo is not a dependency in package.json` (a pass).

## Anti-patterns

- **A `utils/`, `helpers/` or `components/` folder under `src/`.** Pure logic goes to `src/core` or its
  feature, shared UI to `src/ui`, a hook beside the feature that uses it (`src/hooks/` only once two
  features share it).
- **A route that holds its screen** (`useState`, `useEffect`, a `FlatList` and a `fetch` in
  `src/app/explore.tsx`, as the SDK 57 template ships it). Move it to `src/features/<feature>/` with its
  model hook; the route renders it.
- **`const services = createDeviceServices()` in a component or hook.** Each render builds a new device;
  the root layout's `useState(createDeviceServices)` builds it once.
- **A helper, hook or test next to the routes.** Expo Router makes it a route and bundles it; keep
  `src/app` to route files, and test screens and routes in `src/features/` (`<feature>-navigation.test.tsx`).
- **A screen that imports `expo-iap` (or `expo-sqlite/kv-store`, which ESLint's bare-name ban misses).**
  Take the port from `useServices()`; only the adapter imports the SDK.
- **Importing an adapter "just this once" from a store or a model hook.** It hard-wires the device into
  the code and the test; ask for the port and let the composition root decide.
- **`jest.mock('@/services/...')` for the app's own modules.** Pass a fake through `createTestServices()`;
  root `__mocks__/` exist only for vendor SDKs that crash on import.
- **`import type` from `app.config.ts`, `plugins/` or `scripts/` "because it is only a type".** It still
  pulls Node into the app program; move the type to a neutral file in `src/config/`.
- **A debug route that re-exports the debug screen** (`export { DebugScreen as default } from ...`). Expo
  Router bundles it, so the debug folder ships; import `TEST_ONLY` and render `TEST_ONLY?.DebugScreen`.
- **A `require()` of test-only code outside `src/**/test-only.ts`, behind an imported `IS_TEST_BUILD`.**
  Metro keeps the module in production bundles; reach it through the gate's `TEST_ONLY`, whose literal
  comparison app-variants-and-environment checks.
- **`import logo from './logo.png'` or an image under `src/`.** There is no module to import; keep it in
  `assets/images/` and write `require('@/assets/images/logo.png')`.
- **An `expo-*` import with no decision behind it.** Classify it first (`vendorSdks` with a port, or
  `portlessSdks` with the reason); "it is only one call" is how SDKs end up in screens.
- **An API route that imports a store, a screen helper or `react-native`.** It runs on the server; share
  schemas through `src/api/` and logic through `src/core/`, and let the app call the route over HTTP.
- **`import { start } from '@/modules/live-activity'` in a model hook.** A local Expo module is native
  code: put it behind a port and import it only from the adapter.
- **`export const useTasksStore = create(...)`, `export const listsStore = createTasksStore()` or
  `const db = openDatabaseSync(...)` at module level.** Create stores and adapters in factories the root
  layout calls once.
- **A game importing a store, `useServices()` or another game.** The host hands values in through the
  seam; shared pieces move to `src/core` or `src/ui`.
- **Feature A reaching into feature B's files to save a copy.** Move the shared piece down
  (`src/ui`, `src/core`, `src/stores`) or navigate to B's route.
- **A port for pure code.** Ports wrap what cannot run in Jest (SDKs, device APIs, time); a pure function
  stays a function in `src/core`.

## Files in this skill

| File | What it is | Read/run when |
|---|---|---|
| [references/src-layout-and-placement.md](references/src-layout-and-placement.md) | The tree, what each folder holds and imports, the placement table (assets, the test-only gate), route files, the add-a-feature and add-a-folder recipes | Workflow step 1 (placement) |
| [references/dependency-zones.md](references/dependency-zones.md) | The zone graph, what each zone may import, games, what enforces each edge (ESLint and checker), classifying SDKs, Node world and neutral files, assets and `require()`, the test-only gate (verified forms), cycles | Workflow step 1 (imports) |
| [references/ports-and-adapters.md](references/ports-and-adapters.md) | Why ports and when not, the port/adapter/fake anatomy, `Result`, the composition root, injection and testing rules, platform differences, the add-a-port recipe | Workflow steps 1 to 3 |
| [references/contract-and-config.md](references/contract-and-config.md) | Every key of `contracts/architecture.json` and `rn-skills.config.json`, the defaults, exceptions with reasons | Workflow steps 1 and 5 |
| [references/multi-package.md](references/multi-package.md) | When multi-package: workspaces, manifests, TypeScript-source packages, one React, native modules, zones across workspaces | Workflow step 1 (monorepo) |
| `templates/src/services/services-context.tsx` | `Services`, `ServicesProvider`, `useServices()` (the composition-root files and their tests are synced from the library; this skill owns them) | New app; a new port |
| `templates/src/services/services-context.test.tsx` | Its tests (provider, missing provider) | With the context |
| `templates/src/services/create-device-services.ts` | The composition root: every device adapter, once | New app; a new port |
| `templates/src/services/create-device-services.test.ts` | Its tests | With the composition root |
| `templates/src/services/create-test-services.ts` | Test services: the given fakes, and stand-ins that throw on first use | New app; a new port |
| `templates/src/services/create-test-services.test.ts` | Its tests | With it |
| `templates/src/services/clock/` | `ClockPort`, the system adapter, the fake and their tests (synced from the library; do not edit here) | New app; the finished example of a port |
| `templates/src/services/error-log/` | `ErrorLogPort`, the in-memory adapter, the fake and their tests (synced from the library; do not edit here) | New app |
| `templates/src/services/direction-guard/` | `DirectionGuardPort`, its `expo-sqlite/kv-store` adapter, the fake and its test (synced from the library; rtl-and-direction owns it; do not edit here) | New app |
| `templates/port/` | `port.ts` (a vendor-neutral port type, placeholders), `vendor-port-adapter.ts` (the only SDK importer, errors mapped to values), `fake-port.ts` (the in-memory fake with a call counter) and `fake-port.test.ts` | A new port |
| `templates/contracts/architecture.json` | The app's starting architecture contract | New app; an architecture decision |
| `templates/contracts/architecture.schema.json` | Its JSON schema (editor help and the key reference) | With the contract |
| `templates/src/core/result.ts` | `Result`, `ok`, `err`, `attemptAsync` the port templates use (synced from the library; error-handling-typed-results owns it) | Only when the app has none |
| `templates/src/core/dates/date-key.ts` | Calendar-day keys the clock uses (synced from the library; seeded-determinism-and-golden-vectors owns it) | Only when the app has none |
| `templates/src/i18n/languages.ts` | The app's languages and the `Direction` type the guard uses (synced from the library; i18n-strings-and-catalogs owns it) | Only when the app has none |
| [examples/root-layout.tsx](examples/root-layout.tsx) | The root layout mounting the composition root | Workflow step 2 |
| [examples/use-tasks-model.ts](examples/use-tasks-model.ts) | A feature model hook reading ports and a store | Workflow step 3 |
| [examples/architecture-multi-package.json](examples/architecture-multi-package.json) | A contract for a repo with apps and packages | Multi-package repos |
| `scripts/check-boundaries.mjs` | Resolves every import (TypeScript and JavaScript) and checks zones, thin routes (imports and hooks), games, purity, vendor and unclassified SDKs, bans (with the install policy's `doNotAdd`), local Expo modules, adapters and the services factories, fakes, Node world, server code, `require()` and the test-only gate (its entry and `testOnlyFolders`), `../`, unresolved imports and cycles | Workflow step 6 |
| `scripts/check-layout.mjs` | Checks folders, route files, port triples, the composition root and how the root layout mounts it (directly or through an imported startup helper), placement, import-time work (OS mirrors allowed; with or without semicolons), the entry, the aliases (tsconfig and Jest order, explicit or derived by jest-expo), and the multi-package manifests | Workflow step 6 |
| `scripts/lib/architecture.mjs` | Defaults + contract + layout, and the zone of every file | Only when changing a checker |
| `scripts/lib/import-graph.mjs` | Builds and resolves the import graph, Node-world reach, cycles | Only when changing a checker |
| `scripts/lib/source-scan.mjs` | Dependency-free lexer and import resolver (synced from the library; do not edit here) | Only when changing a checker |
| `scripts/check-lib.mjs` | Shared script helper (synced from the library; do not edit here) | Never by hand |
| `scripts/selftest.mjs` | Proves both checkers pass the good fixtures and catch every planted bug | After changing a checker or the defaults |
| `assets/architecture-defaults.json` | The default folders, SDK name patterns, vendor and portless SDKs, bans, adapter, composition-root, game-facing, Node-world and server globs, the server packages, the local-modules folder, the gate's variant, the asset extensions | When a default changes (with eslint.config.mjs) |
| `assets/template-placeholders.json` | Values that fill the port templates in the template harness | When the port templates change |
| `assets/shared.json` | Declares the shared files this skill copies in | When adding a shared file |
| `tests/` | The Tasks-app and multi-package bases and the planted-bug cases (overlay format) | When adding a rule to a checker |

## Related skills

- `typescript-and-lint-rules` - the ESLint zones, vendor and ban lists, and the two TypeScript programs.
- `naming-conventions` - names of the files, ports, adapters and fakes placed here.
- `expo-project-bootstrap` - creating the app with this layout from empty.
- `error-handling-typed-results` - the `Result` type and failure kinds ports return.
- `persistence-and-migrations` - the `SqlDriver` port, repositories and a persistent error-log adapter.
- `state-management-zustand` - stores made by factories and created once.
- `app-startup-and-bootstrap` - the order of work in the root layout around the composition root.
- `unit-and-component-tests` - `renderWithProviders` over `createTestServices()`; route tests with `renderRouterWithProviders`.
- `app-variants-and-environment` - the gate's literal comparison, inclusion flags, `devOnlyRequires`, the bundle proof.
- `dependency-management` - `contracts/dependency-policy.json`, whose `doNotAdd` this skill also bans.
- `debug-menu-and-test-hooks` - what the test-only entry exports.
- `expo-prebuild-and-config-plugins` - `app.config.ts` and local config plugins in `plugins/`.
- `navigation-and-routing` - route files, layouts and typed routes.
- `game-session-host` - the games plugin's seam between a game module and the app; it records the seam's
  files in `gameFacing`.
- `accessibility-as-code` - the OS mirror store (`src/ui/a11y/system-a11y-store.ts`) rule 7 allows.
- `react-components-and-hooks` - the shared cross-feature hooks in `src/hooks/`.
- `skia-canvas-rendering` - the canvas kit in `src/canvas/`.
- `native-modules-and-new-architecture` - writing the local Expo module that `modules/<name>/` holds.
- `web-support` - API routes, EAS Hosting and the web stylesheet.
- `ota-updates` - the `UpdatesPort` over `expo-updates`.

Files in this skill

  • SKILL.md28.6 KB
  • assets/architecture-defaults.json10 KB
  • assets/shared.json1.5 KB
  • assets/template-placeholders.json699 B
  • examples/architecture-multi-package.json632 B
  • examples/root-layout.tsx990 B
  • examples/use-tasks-model.ts2.5 KB
  • references/contract-and-config.md12.1 KB
  • references/dependency-zones.md36.1 KB
  • references/multi-package.md6.6 KB
  • references/ports-and-adapters.md20.8 KB
  • references/src-layout-and-placement.md26.1 KB
  • scripts/check-boundaries.mjs41.7 KB
  • scripts/check-layout.mjs49.7 KB
  • scripts/lib/architecture.mjs28.3 KB
  • scripts/lib/import-graph.mjs7.6 KB
  • scripts/lib/source-scan.mjs35.1 KB
  • scripts/selftest.mjs859 B
  • templates/contracts/architecture.json1.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.