Skip to content
Agentmart
Submit a tool

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

IndexedMIT★ 11
Write a review

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.

Book a call

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.

We use your email only for this newsletter. Privacy

About

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).

No 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

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
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

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.

Goes to the Chalk Labs founders, not the newsletter. Privacy

Need help deploying architecture-and-boundaries?

CallGet help
Cite this page

architecture-and-boundaries skill. Agent Mart. https://agentmart.dev/skills/zakariaf-architecture-and-boundaries (last updated 2026-10-24)