feature-based-architecture
Skill20 starsChanged 14 days agoMITView on GitHub
Organize, scaffold, and refactor full-stack JavaScript/TypeScript codebases around features/domains instead of technical layers, with atomic design on the UI and feature modules on the backend. Use whenever creating a feature, page, component, container, hook, handler, helper, query, API route, endpoint, router, service, or repository; choosing between helpers, utils, common, lib, or infrastructure folders; styling a component or adding a color/spacing value; deciding whether a component belongs in the app or in the design system; wiring server-side rendering (getServerSideProps, React Query initial data); naming files or directories; extracting or sharing code between features; or restructuring a project's folder layout. Trigger even when the user never says "architecture" and only asks "where should this go?", "add X to the project", "style this", or "clean up this utils folder". Not for non-JS/TS projects or bug fixes inside existing files.
- Reads credentials
In this repository21 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- a11y-page-auditSkill
- branch-comment-cleanupSkill
- branch-test-planSkill
- context7-mcpSkill
- env-example-syncSkill
- great-readmeSkill
- harvest-reviewsSkill
- light-dark-modeSkill
- localizationSkill
- minimal-code-changesSkill
- prod-page-auditSkill
- quantum-design-tokensSkill
- reviewable-commitsSkill
- shell-aliases-socialSkill
- skill-best-practices-syncSkill
- skills-auditSkill
- skills-socialSkill
- stacked-pr-splitterSkill
- team-reviewerSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
25 rules. About 0 are common, 16 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈16
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 20
Place code by ownership, not by technical type.
- Very specific: line 40
Never start a parallel features/ tree in a layered project unprompted (see rule 2).
- Distinctive: line 57
Follow Phases 1–6 every time you create, move, or name a file.
- Distinctive: line 61
Before writing anything, inspect the existing structure and record its conventions:
- Distinctive: line 78
Ask what the code *knows about*, not what kind of file it is.
- Distinctive: line 83
Put the file in the owning feature, or in the correct shared location if it has no domain owner.
- Distinctive: line 97
Run the project's linter and type checker.
- Very specific: line 102
Default layout for a frontend app (Next.js pages router shown.
- Distinctive: line 129
Default layout for a Node backend:
- Distinctive: line 159
Create a role file or subfolder only when there is something to put in it.
- Very specific: line 162
Role files grow into folders: Start with helpers.js; when it holds several unrelated responsibilities or passes ~200 lines, split it into …
- Distinctive: line 176
Same noun on both sides: Use the same domain noun for a feature in the frontend and the backend.
- Distinctive: line 183
Ask these questions in order and stop at the first yes.
- Distinctive: line 185
Is it specific to one feature/domain?: Place it inside that feature, in the role matching its job:
- Distinctive: line 206
If none apply, the code probably has an owner you have not identified yet.
- Very specific: line 260
Do not put pure functions like groupBy in infrastructure, and do not put a DB client in utils/.
- Distinctive: line 293
Default to keeping code local.
- Very specific: line 315
Prefer named constants over magic strings for statuses, limits, and ids (IN_PROGRESS_COLUMN_ID, HTTP_STATUS.NOT_FOUND).
- Very specific: line 317
Avoid catch-all names: misc/, stuff/, general/, helpers2/, utils-new/, and a bare shared/ or common/ at the root.
- Very specific: line 328
Every component in one top-level components/ directory by default
- Very specific: line 345
When the placement was a judgment call, tell the user in one line where the file went and why (for example: "Kept reorderTasks in …
- Very specific: line 353
Keep the layered naming (tweetsService.js), mention the option of moving to features to the user, and let them decide.
- Distinctive: line 356
Refactoring: only when the user explicitly asks.
- Distinctive: line 363
Update imports, then run the linter, type checker, and tests after each feature.
- Distinctive: line 364
Leave truly generic code where it is.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Feature-Based Architecture
Place code by ownership, not by technical type.
Golden rule: put code where its knowledge belongs. Code that knows about Task lives
near tasks. Code that knows nothing about the domain may be a utility. Code that connects
the app to storage, a backend, or an external system belongs in a named infrastructure
folder.
Folder names exist to communicate ownership, responsibility, and boundaries. They are not a filing system for file types.
The same rule applies on both sides of the wire: a frontend feature folder and a backend feature folder for the same domain should be recognizably the same thing.
Hard rules (never violate)
- A project
AGENTS.md,CLAUDE.md, or architecture doc overrides this skill. - The existing convention wins over this skill's defaults. A consistent project with slightly different labels is better than one with two competing styles.
- Flag anti-patterns when you see them. Fix them only if the task calls for it.
- Refactor only when the user explicitly asks.
- Never start a parallel
features/tree in a layered project unprompted (see rule 2).
Which reference to load
This file holds the rules that apply everywhere. Load a reference only when the task touches its area:
| Task touches | Load |
| Components, styling, colors/spacing, theming, "is this a design-system component?" | references/design-system.md, plus the quantum-design-tokens skill (dependency) for any token, color, spacing or theme |
| Next.js/React: pages, containers, hooks, handlers, SSR, API routes, React Query, frontend tests | references/frontend-nextjs.md |
| Node backend: Express/Fastify routers, services, repositories, validation, errors, config, backend tests | references/backend-node.md |
Another feature needs this code, or deciding whether to extract it to common/, utils/ or the design system | references/sharing-code.md |
A full-stack feature needs the design-system, frontend and backend references; read references/end-to-end-feature.md too and build in that order.
Follow Phases 1–6 every time you create, move, or name a file.
Phase 1 — Read the project first
Before writing anything, inspect the existing structure and record its conventions:
- Where features live (
features/,src/features/,modules/, or none yet) - Whether the project has an
AGENTS.md,CLAUDE.md, or architecture doc - Naming case for feature folders, component files, and role files (
focusSession/vsfocus-session/,Board/Board.js,tasks.router.ts) - Whether feature roles are single files (
helpers.js,handlers.js) or folders (helpers/) - Whether components are folder + barrel (
Board/index.js) or flat files - What the generic folder is called (
utils/,utilities/) and what is actually in it - Which infrastructure folders exist (
api/,datasources/,config/,lib/) and what each one owns - Whether UI primitives come from an external design-system package
- Language and module system (JS/TS, ESM/CommonJS,
.jsextensions in TS imports)
Phase 2 — Identify the owner
Ask what the code knows about, not what kind of file it is. See Placement decision.
Phase 3 — Place it
Put the file in the owning feature, or in the correct shared location if it has no domain owner.
Phase 4 — Name it
Match the project's established style. See Naming.
Phase 5 — Check the dependency direction
Confirm the new imports point the allowed way. See Dependency direction.
Phase 6 — Verify
Run the project's linter and type checker. Fix any import that breaks the direction rules or the build, then re-run until both pass; report failures that predate this change.
Where code lives
Default layout for a frontend app (Next.js pages router shown; adapt to the framework):
features/
├── tasks/ domain feature
│ ├── components/ presentational: Board/Board.js + Board/index.js
│ ├── containers/ stateful wiring: hooks + handlers → components
│ ├── hooks/ useTasks.js, useDeleteConfirmation.js
│ ├── handlers.js createAddTaskHandler, createDeleteTaskHandler
│ ├── helpers.js pure domain functions
│ ├── queries.js server-side domain reads (SSR + API routes)
│ └── constants.js statuses, limits, ids
├── planning/ screen/flow feature: composes domain features
│ ├── components/
│ └── containers/Planning.js
└── common/ React code shared by ≥2 features
├── components/ MainLayout, UserHeader
├── hooks/ useDialog, useLocalData
└── api.js the one entry point to the HTTP client
pages/ thin routes: getServerSideProps + render a container
api/ infrastructure: browser HTTP client
datasources/ infrastructure: server-side storage access
config/ infrastructure: env reads, the one place process.env is read
utils/ generic, domain-free functions
styles/ global styles and token overrides onlyDefault layout for a Node backend:
src/
├── features/
│ ├── tasks/
│ │ ├── Task.ts domain types + domain constants
│ │ ├── tasks.router.ts HTTP: routes, input parsing, status codes
│ │ ├── tasks.service.ts business operations, returns domain types
│ │ ├── tasks.schema.ts (on demand) request validation
│ │ └── tasks.repository.ts (on demand) persistence
│ └── common/ cross-feature domain pieces (rare)
├── lib/ db connection, logger
├── utils/ httpStatus.ts and other domain-free code
├── middlewares/ error pipeline, not-found, validation factory
├── config.ts
├── app.ts builds the app (testable)
└── server.ts connects + listens| Location | Holds | Knows the domain? |
features/<domain>/ | Everything that exists because that domain exists | Yes |
features/<screen>/ | A screen or flow that composes domain features (planning, retrospective) | Yes |
features/common/ | React components/hooks (or backend pieces) shared by two or more features | Barely: shared app concepts, no single owner |
utils/ | Generic functions that could be copied into an unrelated project; one function per file, named after it (isEmpty.js), grouped only for constant tables (time.js, httpCodes.js) | No |
Named infra folders (api/, datasources/, config/, src/lib/) | Clients, storage access, env, SDK wrappers | No (they serve the domain without encoding its rules) |
| Design-system package | Atoms and molecules, tokens | No |
pages/ / app/ | Framework routing, kept thin | Composes features only |
Create a role file or subfolder only when there is something to put in it. A feature with two files does not need seven empty slots.
Role files grow into folders. Start with helpers.js; when it holds several unrelated
responsibilities or passes ~200 lines, split it into helpers/<name>.js. Do the same for
handlers.js, queries.js, constants.js. Keep the import surface stable while doing
it.
Domain features vs screen features. Name domain features after the domain (tasks,
focusSession). A screen/flow feature (planning, retrospective) is legitimate when it
owns containers and screen-specific components that compose several domains. Screen
features may also have their own hooks/, helpers.js, and constants.js for logic that
only that screen needs. Screen features import domain features; domain features never
import screen features. A page about a single domain (a list of finished focus sessions)
does not need a screen feature: its container lives in the domain feature. Create a screen
feature when a page composes two or more domains.
Same noun on both sides. Use the same domain noun for a feature in the frontend and
the backend. Singular vs plural follows each side's existing convention
(features/focusSession/ in the web app, src/features/focusSessions/ in the API) —
don't rename an existing feature to align them.
Placement decision
Ask these questions in order and stop at the first yes.
- Is it specific to one feature/domain? Place it inside that feature, in the role
matching its job:
- renders UI from props →
components/ - wires hooks and handlers to components →
containers/ - React state, effects, or React Query →
hooks/ - builds an event handler from dependencies →
handlers.js - pure domain logic, no I/O →
helpers.js - server-side domain read used by SSR and/or API routes →
queries.js - fixed values, statuses, limits →
constants.js - (backend) HTTP concern →
<feature>.router.ts; business operation →<feature>.service.ts; persistence →<feature>.repository.ts; input shape →<feature>.schema.ts; types →<Entity>.ts
- renders UI from props →
- Does it connect the app to storage, a backend, or an external system? (HTTP client,
DB client, auth, analytics, storage, SDK wrapper) → the matching infrastructure folder;
lib/if none fits. - Is it a React component or hook shared by two or more features? →
features/common/. - Is it a domain-free UI primitive (button, input, card, modal)? → the design-system package, not the app. See references/design-system.md.
- Is it a domain-free function? →
utils/.
If none apply, the code probably has an owner you have not identified yet. Look again at step 1 before inventing a new top-level folder.
Examples
reorderTasks(tasks, …)→features/tasks/helpers.js. It takesTasks.formatMilliseconds(ms)→utils/. It knows nothing about tasks or sessions.createAddTaskHandler({ tasks })→features/tasks/handlers.js.readTasks({ sessionId })used bygetServerSidePropsand an API route →features/tasks/queries.js.- The Redis client →
datasources/memory/client.js. Infrastructure. useIntervalused by two features →features/common/hooks/.useTasks→features/tasks/hooks/.- A generic
Badge→ design-system package. ATaskCounterBadgethat takes aTask[]→features/tasks/components/. PATCH /tasks/:id/archive(only completed tasks) →tasks.service.ts(archiveTask: returnsnullif missing, throws a typed conflict error if not completed) andtasks.router.ts(route;null→ 404; the error pipeline maps the conflict to 409).
Helpers vs utils
The single test: does this function know about the application's domain?
- Yes → helper. It takes domain objects, uses domain terminology (task, session, status), or encodes a business rule. Keep it inside its feature.
- No → util. It could be copied into an unrelated project and still make sense.
A global utils/ containing task.js, session.js, and user.js hides who owns each
behavior. Nobody knows what is safe to change, and the folder grows without limit. The aim
is not to eliminate helpers; it is to make their owner obvious.
Infrastructure is not utils
Infrastructure is the machinery the app talks through: HTTP clients, storage adapters,
DB connections, env config, SDK wrappers. Give each piece a precisely named folder when
the project has more than one (api/ for the browser HTTP client, datasources/ for
server storage, config/ for env). Use lib/ as the home for infrastructure that has no
better name.
The designated infrastructure homes (lib/, middlewares/, config) may be created for
their first file — that is not the "new top-level folder for a single file" anti-pattern,
which targets invented, vaguely named folders.
Separate the infrastructure from the domain operations performed against it:
api/request.js ← how the browser talks to the backend
features/common/api.js ← the one entry point features use for it
datasources/index.js ← how the server reaches storage
features/tasks/queries.js ← what the tasks domain reads through it
src/lib/db.ts ← backend DB connection
src/features/tasks/tasks.service.ts ← what tasks does with itDo not put pure functions like groupBy in infrastructure, and do not put a DB client in
utils/. Do not create per-feature api.js re-exports of the shared HTTP client.
Dependency direction
Imports point inward toward more generic code, never outward toward more specific code.
pages/ → containers/ → components/ → design-system package
│
├→ hooks/ → features/common/api.js → api/
├→ handlers.js
└→ helpers.js, constants.js
pages/ (SSR) → features/*/queries.js → datasources/
backend: router → service → repository/model → src/lib/
everything may import → utils/, config/Import rules:
utils/never imports fromfeatures/, infrastructure, or the framework's routing.- Infrastructure (
api/,datasources/,lib/) never imports fromfeatures/. - Presentational
components/never call data hooks or the HTTP client; containers do. - Domain features never import screen features.
- A backend service never imports a router; a router never touches the DB model directly.
- A feature that uses another feature imports what it needs explicitly. If the project
gives features a public entry point (
index.js), go through it; if not, import the specific file and keep cross-feature imports few and visible.
Sharing code between features
Default to keeping code local; extraction is a decision, not a reflex. When a second
feature needs something, read references/sharing-code.md
before moving it. Prefer duplication over a wrong abstraction, and never move code to
utils/ for hypothetical reuse.
Naming
- Match the project's existing case and style for directories and files. Never introduce a second style.
- Defaults when the project has none:
- Frontend feature folders: camelCase (
focusSession/). Components: PascalCase folder + file + barrel (Board/Board.js,Board/index.js). Hooks:useThing.js, one per file, default export. Role files: lowercase (handlers.js,helpers.js,queries.js,constants.js). Route files: kebab-case (pages/focus-session.js). - Backend feature folders: camelCase (
focusSessions/). Role files:<feature>.<role>.ts(tasks.router.ts,tasks.service.ts). Domain types: singular PascalCase (Task.ts). URL prefixes: kebab-case (/focus-sessions).
- Frontend feature folders: camelCase (
- Name features after the domain or the user-facing flow, never after a technical concern.
- Name functions after what they do:
canEditTask,readTasks,createAddTaskHandler, nottaskUtils. - Name factories
create<Thing>(createAddTaskHandler,createValidationMiddleware); name event propson<Event>. - Prefer named constants over magic strings for statuses, limits, and ids
(
IN_PROGRESS_COLUMN_ID,HTTP_STATUS.NOT_FOUND). - Avoid catch-all names:
misc/,stuff/,general/,helpers2/,utils-new/, and a bareshared/orcommon/at the root.features/common/is allowed only with the meaning defined above; document it in the project's architecture file.
A vague name is an invitation to dump unrelated code. If you cannot name a folder precisely, the grouping is probably wrong.
Anti-patterns
- A global
utils/whose files are named after domain entities, orutils/importing fromfeatures/ - Every component in one top-level
components/directory by default lib/used as a synonym forutils/- A new top-level folder created to hold a single file
- A helper moved to shared code after its first reuse without evaluation
- A feature reaching into another feature's internals when a public entry point exists
- Domain logic living in route/page files, or business rules living in a backend router
- A presentational component calling the HTTP client or a data hook
- A UI primitive (button, input, card) built inside the app instead of the design system
- Hard-coded colors, spacing, radii, or font sizes instead of tokens
getServerSidePropsfetching the app's own API routes over HTTP- A backend router talking to the DB model directly, or a service returning raw ORM documents
process.envread outside the config module- The same function copy-pasted twice inside one project
Report
When the placement was a judgment call, tell the user in one line where the file went and
why (for example: "Kept reorderTasks in features/tasks/helpers.js because it takes a
Task").
Notes & edge cases
- Layered project (no features directory; everything under
components/,hooks/,utils/orroutes/,services/,repositories/): follow the layout that exists. Keep the layered naming (tweetsService.js), mention the option of moving to features to the user, and let them decide. Refactoring: only when the user explicitly asks. Then:
- Map current files to their owning features and share the plan before moving anything. For a layered backend, see "Mapping a layered project" in backend-node.md.
- Migrate one feature at a time.
- Move files without changing behavior. Keep structural changes separate from logic changes so the diff stays reviewable.
- Update imports, then run the linter, type checker, and tests after each feature.
- Leave truly generic code where it is; move only code with a domain owner.
- Report anything ambiguous instead of guessing its owner.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

