business-logic-entry-point-colocate-types
Colocate Types with Business Logic Entry Points
Goal
When a business-logic entry point has parameters or return values whose types are declared by the project, those types must live in the same file as the entry point, as long as the project stack allows it.
This keeps each entry point self-contained: opening one file reveals the full signature, including the shape of its inputs and outputs, without navigating to other files.
What Counts as In Scope
Apply this skill to code that does one or more of these things:
- declares a business-logic entry point with project-declared parameter types or return types
- places command types, query types, success types, or error types in separate files from the entry point that uses them
- creates shared type modules or barrel exports for types that belong to a single entry point
- imports project-declared signature types from another file into an entry point module
What Counts as a Project-Declared Type
A project-declared type is a type that was created within the project specifically for use in the codebase. This includes:
- command types, query types, success types, error types
- DTOs, value objects, or payload types declared for a specific entry point
- any type alias, interface, struct, class, enum, or equivalent declared by the project
This does not include:
- types from the language's standard library
- types from third-party libraries or frameworks
- domain-entity types that are shared across multiple entry points
- primitive types or built-in generic types
The Rule
-
Project-declared types used in the signature of a business-logic entry point must be declared in the same file as that entry point.
- The command or query type goes in the same file as the handler.
- The success type goes in the same file as the handler.
- The error type goes in the same file as the handler, unless it is shared across multiple entry points.
-
Do not create separate files for types that belong to a single entry point.
- Do not place
CreateReservationCommandin acreate-reservation-command.tsfile and the handler in acreate-reservation-command-handler.tsfile. - Keep both in the same file.
- Do not place
-
Types shared across multiple entry points are exempt.
- Domain-entity types, shared error types, shared value objects, and other types used by more than one entry point may live in their own modules.
- This rule applies only to types that are specific to a single entry point's signature.
-
Apply this rule only when the project stack allows it.
- Some languages or frameworks impose file-per-type conventions (e.g., Java's one-public-class-per-file rule).
- In those cases, follow the language or framework convention instead.
- When the language allows multiple type definitions per file, apply this rule.
Examples
Prefer this:
// create-reservation-command-handler.ts
type CreateReservationCommand = {
requesterId: RequesterId
carClass: CarClass
startsAt: ZonedDateTime
endsAt: ZonedDateTime
}
type CreateReservationCommandHandlerSuccess = {
reservationId: ReservationId
}
export function createReservationCommandHandler(
command: CreateReservationCommand,
): ResultAsync<CreateReservationCommandHandlerSuccess, CreateReservationCommandHandlerError> {
// business logic
}
# create_reservation_command_handler.py
@dataclass(frozen=True)
class CreateReservationCommand:
requester_id: RequesterId
car_class: CarClass
starts_at: ZonedDateTime
ends_at: ZonedDateTime
@dataclass(frozen=True)
class CreateReservationCommandHandlerSuccess:
reservation_id: ReservationId
def create_reservation_command_handler(
command: CreateReservationCommand,
) -> CreateReservationCommandHandlerSuccess:
# business logic
Avoid this:
# Types in a separate file
create-reservation-command.ts ← command type
create-reservation-command-handler.ts ← handler imports the type
# Types in a shared barrel
types/commands.ts ← all command types together
handlers/create-reservation.ts ← handler imports from barrel
Detection Workflow
-
Find the business-logic entry point.
- Identify the handler function, use case, or application service method.
-
Inspect the signature types.
- List the parameter types and return types in the signature.
- Determine which of those types are project-declared.
-
Check where each project-declared type is declared.
- If a project-declared signature type is in a different file from the entry point, it violates this rule.
- If the type is shared across multiple entry points, it is exempt.
-
Check the project stack.
- If the language enforces file-per-type conventions, the rule does not apply.
Writing or Changing Entry Points
-
Declare signature types in the same file as the entry point.
- Write the command or query type, success type, and entry-point-specific error types in the same file as the handler.
-
Do not pre-create type files.
- When starting a new entry point, create one file and put everything in it.
-
Extract a type to its own module only when it becomes shared.
- If a second entry point needs the same type, move the type to its own module at that point.
- Until then, keep it colocated.
Review Questions
When reading or reviewing code, ask:
- Are there project-declared types in the entry point's signature that live in a different file?
- Are those types used by only this entry point?
- Does the project stack allow multiple type definitions per file?
- Would moving those types into the entry point's file make the module self-contained?
If the answer is yes, apply this skill.
Report the Outcome
When finishing the task:
- state which entry points were identified or changed
- state which types were colocated into the entry point's file
- state which types remained in separate modules because they are shared
- state whether the project stack imposed any constraints on colocation
More from code-sherpas/agent-skills
neverthrow-wrap-exceptions
Capture exceptions and promise failures with `neverthrow` instead of hand-written `try/catch` in TypeScript and JavaScript code. Use when wrapping synchronous functions that may throw, promise-returning functions that may throw before returning, existing `PromiseLike` values that may reject, or third-party APIs such as parsers, database clients, HTTP clients, file-system helpers, serializers, and SDK calls. Prefer `Result.fromThrowable` for synchronous throwers, `ResultAsync.fromThrowable` for promise-returning functions that may throw or reject, and `ResultAsync.fromPromise` when you already have a `PromiseLike` value in hand. Only keep `try/catch` when the language construct, cleanup requirement, or framework boundary truly requires it.
11atomic-design
Create or update web UI components with a strict reuse-first workflow. Use when building, refactoring, restyling, or extending frontend or template components while minimizing raw DOM or HTML by reusing or generalizing existing components first.
10write-persistence-representations
Create or update persistence-layer data representations in any stack, including ORM entities, schema definitions, table mappings, document models, collection definitions, and similar database-facing code. Use when agents needs to add or change persisted fields, identifiers, relationships, indexes, timestamps, auditing fields, or storage mappings in frameworks, libraries, or ORMs such as Prisma, TypeORM, Sequelize, Drizzle, Mongoose, Hibernate/JPA, Doctrine, Ecto, Active Record, or equivalent persistence technologies.
7business-logic
Identify, interpret, review, or write business logic in code. Use when an agent needs to decide whether code expresses business rules, business algorithms, or business workflows, or when it must implement, preserve, or refactor code that creates, stores, or transforms data according to real business policies.
7immutable-domain-entities
Require the immutable design pattern for domain entities. Use when an agent needs to create, modify, review, or interpret domain entities and should preserve identity while expressing state changes through new immutable instances. Domain entities must be modeled as immutable classes, not as plain type aliases or interfaces paired with standalone functions.
7update-agent-skills
Update agent skills installed with the `skills` CLI. Use when asked to refresh installed skills, keep a project's skills current, or troubleshoot cases where `npx skills update` reports that everything is up to date. For project-scoped installs, a no-change update must immediately run the bundled reinstall script so tracked skills from `skills-lock.json` are reinstalled without extra investigation.
7