architecture-and-boundaries
Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root.
Skill by zakariaf
Chalk Labs
Need help deploying architecture-and-boundaries?
Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/zakariaf-architecture-and-boundaries
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
- Runs as
- Node.js package
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/architecture-and-boundaries
What's inside
- Skill 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).
- License
- MIT
- Instructions
- 295 lines in SKILL.md · view on GitHub
Before installing
- Ships executable scripts
Files (137)
- SKILL.md
- architecture-defaults.json · assets/
- shared.json · assets/
- template-placeholders.json · assets/
- architecture-multi-package.json · examples/
- root-layout.tsx · examples/
- use-tasks-model.ts · examples/
- contract-and-config.md · references/
- dependency-zones.md · references/
- multi-package.md · references/
- ports-and-adapters.md · references/
- src-layout-and-placement.md · references/
- check-boundaries.mjs · scripts/
- check-layout.mjs · scripts/
- check-lib.mjs · scripts/
- architecture.mjs · scripts/lib/
- import-graph.mjs · scripts/lib/
- source-scan.mjs · scripts/lib/
- selftest.mjs · scripts/
- architecture.json · templates/contracts/
- architecture.schema.json · templates/contracts/
- fake-port.test.ts · templates/port/
- fake-port.ts · templates/port/
- port.ts · templates/port/
- vendor-port-adapter.ts · templates/port/
- date-key.ts · templates/src/core/dates/
- result.ts · templates/src/core/
- languages.ts · templates/src/i18n/
- clock-port.ts · templates/src/services/clock/
- fake-clock.test.ts · templates/src/services/clock/
- fake-clock.ts · templates/src/services/clock/
- local-date.test.ts · templates/src/services/clock/
- local-date.ts · templates/src/services/clock/
- system-clock-adapter.test.ts · templates/src/services/clock/
- system-clock-adapter.ts · templates/src/services/clock/
- create-device-services.test.ts · templates/src/services/
- create-device-services.ts · templates/src/services/
- create-test-services.test.ts · templates/src/services/
- create-test-services.ts · templates/src/services/
- direction-guard-port.ts · templates/src/services/direction-guard/
- fake-direction-guard.test.ts · templates/src/services/direction-guard/
- fake-direction-guard.ts · templates/src/services/direction-guard/
- kv-direction-guard-adapter.ts · templates/src/services/direction-guard/
- error-log-port.ts · templates/src/services/error-log/
- fake-error-log.test.ts · templates/src/services/error-log/
- fake-error-log.ts · templates/src/services/error-log/
- memory-error-log-adapter.test.ts · templates/src/services/error-log/
- memory-error-log-adapter.ts · templates/src/services/error-log/
- services-context.test.tsx · templates/src/services/
- services-context.tsx · templates/src/services/
- SKILL.md · tests/bases/app/.agents/skills/other-skill/
- leak.ts · tests/bases/app/.agents/skills/other-skill/src/core/
- expo-haptics.ts · tests/bases/app/__mocks__/
- app.config.ts · tests/bases/app/
- jest.config.js · tests/bases/app/
- package.json · tests/bases/app/
- with-app-name.ts · tests/bases/app/plugins/
- package.json · tests/bases/app/scripts/
- count-tasks.ts · tests/bases/app/scripts/quality/
- SKILL.md · tests/bases/app/skills/demo-skill/
- …and 77 more
SKILL.md (first lines)
# 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) |From zakariaf/ReactNative-Skills, MIT.
More from zakariaf/ReactNative-Skills
- 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
async-safety
zakariaf
Prevents silent async bugs: effect races, stale results, lost promises, leaked listeners and timers, AbortSignal gaps.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
audio-and-haptics-feedback
zakariaf
Builds SFX and haptics behind ports.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
auth-and-secure-storage
zakariaf
Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
background-tasks
zakariaf
Builds expo-background-task jobs: idempotent, budgeted runs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
ci-pipeline-and-gates
zakariaf
Builds GitHub Actions CI for Expo apps - verify on every PR, SHA-pinned actions, pinned runners and Node, device lanes, manual EAS releases.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9
Signals
- Stars
- 11
- Forks
- 2
- Open issues
- 0
- Last commit (this folder)
- Oct 17, 2026 (13 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/zakariaf/ReactNative-Skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from zakariaf
- 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9
Done for you
Rather not set it up yourself?
Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.
See how we build AI agents →Read the case studies
Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.
- launches shipped
- 50+
- launches shipped
- media placements
- 500+
- media placements
- founders on every account
- 2
- founders on every account
Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block
Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.
Get architecture-and-boundaries running in your business
Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.
Cite this page
architecture-and-boundaries skill. Agent Mart. https://agentmart.dev/skills/zakariaf-architecture-and-boundaries (last updated 2026-10-24)