From development-skills
Enforces code organization into features/ (verticals), platform/ (horizontals), and shell/ (wiring). Guides file placement via decision tree for refactoring, new files, structure questions.
npx claudepluginhub ntcoding/claude-skillz --plugin fetching-circleci-logsThis skill uses the workspace's default tool permissions.
**Vertical** = all code for ONE feature, grouped together
Applies Clean Architecture, Hexagonal (Ports & Adapters), and DDD fundamentals to design systems, define layer boundaries, tactical patterns (entities, aggregates, repositories), enforce dependency rules.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Vertical = all code for ONE feature, grouped together Horizontal = capabilities used by MULTIPLE features
features/ — verticals, containing some combination of entrypoint/, commands/, queries/, domain/, infra/
platform/ — horizontals, contains domain/ and infra/
shell/ — app wiring/routing only (no business logic). Registers routes, bootstraps frameworks, connects message brokers. Not a package entry point — libraries use src/index.ts for that.src/index.ts — library package entry point. Pure barrel file (only re-export statements, no logic). Only needed for packages consumed by other packages.Note on terminology: CLI subcommands (like git commit) are wired in shell/. Write operations in commands/ are CQRS commands — different concepts.
features/ platform/ shell/
├── checkout/ ├── domain/ └── cli.ts
│ ├── entrypoint/ │ └── tax-calc/
│ ├── commands/ └── infra/
│ ├── queries/ ├── external-clients/
│ ├── domain/ ├── http/
│ └── infra/ ├── cli/
│ ├── mappers/ ├── persistence/
│ └── persistence/ ├── config/
│ └── logging/
└── refunds/
├── entrypoint/
├── commands/
├── queries/
└── domain/
Libraries use the same features/ + platform/ structure. The package is NOT the feature — still wrap in features/{name}/. Libraries don't need shell/ unless they wire an app.
src/
├── index.ts ← barrel (pure re-exports, no logic)
├── features/
│ └── extraction/
│ ├── queries/
│ ├── domain/
│ └── infra/
└── platform/
├── domain/
└── infra/
Small utility/config/schema packages with no domain logic may be excluded from this architecture entirely.
🚨 When unsure where code belongs, follow this decision tree. Stop at the first match.
Registers routes, bootstraps a framework, connects to a message broker, registers CLI subcommands with a framework.
→ shell/
Test: If you deleted this code, would the app still have all its logic but no way to start?
❌ Not shell/ if: It parses input, formats output, contains business logic, or loads/saves data. Those are deeper layers. Also not shell/ if it's a package barrel file re-exporting types for consumers — that's src/index.ts.
Parses HTTP requests, CLI arguments, or queue messages into internal types. Formats internal results into HTTP responses, CLI output (tables, JSON, plain text), or outgoing messages. Maps domain errors to status codes or exit codes. Handles interactive prompts, progress bars, spinners.
→ entrypoint/
Test: If you changed protocols (HTTP → CLI, CLI → queue consumer, etc.), would you rewrite this code but keep commands/ and domain/ unchanged?
❌ Not entrypoint/ if: It loads data, modifies state, persists results, talks to a database, or enforces business rules. If you see load→modify→save, that's commands/, not entrypoint/.
Loads data, invokes domain logic to modify it, then persists the result. Or coordinates a side-effect through an external service (payment, email, deployment).
→ commands/
Test: Does it change state? Would "Undo" make sense for this operation?
❌ Not commands/ if: It parses external input (HTTP requests, CLI args, queue messages) — that's entrypoint/. It contains the business rules themselves — that's domain/. It only reads data — that's queries/.
Loads data, transforms/aggregates it, returns a result. No side effects, no state changes.
→ queries/
Test: Could you run this 100 times with the same input and get the same result (assuming no external writes)?
❌ Not queries/ if: It writes, deletes, sends emails, triggers side effects, or enforces invariants.
Validation rules, state transitions, invariants, domain calculations that only this feature cares about. Repository interfaces (domain contracts for persistence) also live here.
→ features/{name}/domain/
Test: Does another feature need this? If no → feature domain. If yes → keep reading.
❌ Not feature domain/ if: It orchestrates persistence (that's commands/) or is needed by multiple features (that's platform/domain/ or a dedicated domain library package).
Repository implementations, response mappers, format adapters, feature-specific middleware. Implements domain contracts or handles protocol/format concerns for this feature only.
→ features/{name}/infra/
Test: Is this technical plumbing (not business rules) that only this feature needs?
❌ Not feature/infra/ if: It contains business rules (that's domain/). It's used by multiple features (that's platform/infra/). It parses external input or invokes commands (that's entrypoint/).
Contains project-specific domain language (your entity names, your business concepts, your workflow terms)?
→ platform/domain/ (or a dedicated domain library package)
Test: Would a new developer need to understand your business to understand this code?
❌ Not platform/domain/ if: It's generic infrastructure with no project-specific concepts.
Shared value objects (Money, Email, Address) that enforce validation → platform/domain/ or a dedicated domain library.
Shared technical concerns (HTTP clients, database wrappers, logging, config, response formatters, shared middleware)?
→ platform/infra/
Platform/infra/ includes both generic utilities and project-specific conventions for infrastructure concerns (response formatters, error handling middleware).
Test: Is it infrastructure that multiple features or entrypoints use?
❌ Not platform/infra/ if: It contains business rules or domain invariants. That's platform/domain/.
What: Each layer can only depend on layers deeper than itself. Never depend on layers above you.
Direction: entrypoint → commands/queries → domain. Infrastructure supports all layers but domain never depends on infrastructure.
| From | Can depend on | Forbidden |
|---|---|---|
| entrypoint/ | commands/, queries/, own feature/infra/, platform/infra/ (restricted — see SoC-012) | domain/, platform/domain/ |
| commands/ | domain/, platform/infra/, platform/domain/, own feature/infra/ | entrypoint/, other features |
| queries/ | domain/ (read-only), platform/infra/, platform/domain/, own feature/infra/ | entrypoint/, commands/ |
| domain/ | platform/domain/ | all infra/ (feature or platform), entrypoint/, commands/, queries/ |
| shell/ | entrypoint/ (to wire routes) | commands/, queries/, domain/ directly |
| src/index.ts (barrel) | any internal module (re-exports only) | must not contain logic |
What: Code that belongs to one feature stays in that feature's folder. Code used across features lives in platform/ or a dedicated domain library package.
Why: When shared logic is buried in one feature, other features either import across boundaries (coupling) or duplicate the logic (divergence). Both cause bugs.
How:
❌ BAD - buried in one feature:
features/checkout/tax-calculator.ts
features/refunds/refund.ts ← imports ../checkout/tax-calculator
❌ BAD - duplicated:
features/checkout/tax-calculator.ts
features/refunds/tax-calculator.ts ← rules diverge over time
✅ GOOD - extracted to platform:
features/checkout/
features/refunds/
platform/domain/tax-calculation/ ← shared domain logic
Query-only features: If queries need to be shared across features, extract to a dedicated query library package — cross-feature imports are forbidden.
domain/ contains pure business logic. No database access, no HTTP calls, no file system, no external services. Domain defines contracts (interfaces/types) that infrastructure implements. Domain never imports from any infra/. Repository interfaces live in domain/; implementations live in infra/.
Commands orchestrate write operations: load → delegate to domain → persist. Business rules (validation, state transitions, invariants) belong in domain/. Commands MAY invoke external services directly when no business rules are involved.
Why: Domain invariants must be enforced by domain objects, not scattered across command files.
Violation signals: conditionals on business state, inline validation, calculations, invariant checks in command files.
Naming: Imperative verb phrase — place-order.ts, cancel-subscription.ts, approve-refund.ts. Menu test: would this appear on a UI menu?
Entrypoints translate between external world and commands/queries: parse external input → invoke command/query → map result to external response. Nothing else.
Entrypoints own: input parsing, output formatting, interactive prompts (progress bars, spinners), exit code mapping. When entrypoint/ grows large, extract infrastructure helpers to features/{name}/infra/.
Violation signals: orchestration logic, domain rules, data fetching, or database access in entrypoint files.
Each command defines its own dedicated input type. No sharing of input DTOs between commands. No dependency on external input types.
Why: Shared input types create coupling — changing one command's input breaks another. External types (HttpRequest, CLI arg objects) leak protocol concerns into commands.
Violation signals: HttpRequest, CLI arg objects, or raw message payloads passed to commands. Multiple commands sharing one input type.
Queries handle read operations with no side effects. Can query database directly (no repository required) or load domain objects read-only.
Why minimal layering: Queries don't mutate state — no invariants to protect. Optimize for read performance and simplicity.
Violation signals: writes, deletes, emails, side effects, or invariant enforcement in query files.
Naming: Read-operation prefix — get-order-summary.ts, list-pending-refunds.ts, search-products.ts. Query-only features need only queries/, no domain/ required.
commands/ contains ONLY command files. queries/ contains ONLY query files. No helpers, utilities, or nested folders. If logic doesn't fit in the command/query itself, it belongs in domain/ or infra/.
What: Files used together live together. Never group by category.
Why: Type-based grouping scatters related code. One change = many folders. Co-location means one change = one folder.
How:
Forbidden everywhere: types/, models/, validators/, assertions/, schemas/, interfaces/, value-objects/, and their single-file equivalents.
Exception: Shared test fixtures used across multiple test files may live in a fixtures/ file or folder.
Generic wrappers for external services (APIs, databases, SDKs) live separately from code that uses them in domain-specific ways.
Test: "Would the creators of this external service recognize this code?" YES → platform/infra/external-clients/. NO → your domain code.
What: All infra/ directories (feature and platform) use standard sub-folders. Only add non-standard sub-folders when logic genuinely doesn't fit these locations.
🚨 CRITICAL: No files at infra/ root. Everything must be in a sub-folder. Dumping files at infra/file.ts is forbidden.
| Sub-folder | Contains |
|---|---|
external-clients/ | Wrappers for third-party libraries used by this feature only (ts-morph, git, etc.). Same concept as platform/infra/external-clients/ but scoped to one feature |
mappers/ | Response/format mapping (domain result → external format) |
middleware/ | Feature-specific middleware (validation, rate limiting for this feature only) |
persistence/ | Repository implementations (the concrete database code behind domain contracts) |
| Sub-folder | Contains |
|---|---|
external-clients/ | Third-party service wrappers (Stripe, SendGrid, AWS SDKs). NOT for stdin/stdout or OS-level I/O — those go in cli/ or http/ |
middleware/ | Shared middleware used across features (auth, CORS, request logging) |
persistence/ | Database clients, connection pools, shared query builders |
http/ | Shared HTTP formatters, error handling middleware, response utilities |
cli/ | CLI I/O utilities (stdin readers, terminal formatting, TTY detection, arg parsing helpers, progress bars) |
messaging/ | Queue clients, event bus, pub/sub infrastructure |
config/ | Configuration loading, environment variable parsing |
logging/ | Structured logging, log formatters |
Which layers can access which infra sub-folders:
| Sub-folder | entrypoint/ | commands/ | queries/ | domain/ |
|---|---|---|---|---|
persistence/ | ❌ | ✅ | ✅ | ❌ |
external-clients/ | ❌ | ✅ | ✅ | ❌ |
http/ | ✅ | ❌ | ✅ | ❌ |
cli/ | ✅ | ❌ | ❌ | ❌ |
messaging/ | ✅ | ✅ | ❌ | ❌ |
config/ | ✅ | ✅ | ✅ | ❌ |
logging/ | ✅ | ✅ | ✅ | ❌ |
external-clients/ (feature) | ❌ | ✅ | ✅ | ❌ |
mappers/ | ✅ | ❌ | ❌ | ❌ |
middleware/ (feature) | ✅ | ❌ | ❌ | ❌ |
middleware/ (platform) | ✅ | ❌ | ❌ | ❌ |
High-level flow should be visible at one abstraction level without reading implementation details of each step.
Violation signals: methods where you can't describe the overall flow without reading every line; inline error handling or rollback logic that obscures the happy path sequence.
Functions that depend on different state (different fields, databases, services) belong in different modules.
Violation signals: a class where methods cluster around different subsets of dependencies; constructor with unrelated dependencies used by different method groups.
Functions in the same module should have names that relate to a common concept. If you can't name the module after what the functions have in common, they don't belong together.
Violation signals: module names ending in -helpers, -utils, -service containing unrelated operations; functions that change for different business reasons grouped together.
When designing, implementing, refactoring, or reviewing code, verify each applicable rule.
For code/architecture reviews: Evaluate each file against SoC-001 through SoC-015. Verdict per rule: PASS, FAIL (cite file:line), or N/A.
| Code | Rule | Applies to |
|---|---|---|
| SoC-001 | Always follow the code placement decision tree | All files |
| SoC-002 | Dependencies point inward | All layer files |
| SoC-003 | Features never cross-import | features/ |
| SoC-004 | Domain never does I/O | domain/ |
| SoC-005 | No business logic in commands | commands/ |
| SoC-006 | Entrypoints are thin translation layers | entrypoint/ |
| SoC-007 | Commands own their inputs | commands/ |
| SoC-008 | Queries read, never write | queries/ |
| SoC-009 | No helpers in commands or queries | commands/, queries/ |
| SoC-010 | Co-locate by change, not kind | All |
| SoC-011 | External wrappers in platform/infra | platform/ |
| SoC-012 | Infra uses standard sub-folders | infra/ |
| SoC-013 | Separate intent from execution | All |
| SoC-014 | Separate functions that depend on different state | All |
| SoC-015 | Separate functions that don't have related names | All |
Each code references detailed rules in the sections above. Do not proceed until all applicable rules pass.