File naming and project structure
Invokta engines use filenames to make dependency direction visible. A reader should be able to tell whether a module defines a public capability, owns a business rule, declares an outbound boundary, wraps a provider, or starts a transport before opening the file.
This convention is guidance for custom engines and Invokta examples. Invokta
does not inspect or enforce project paths or filenames at runtime. The engine
still uses explicit imports, factory arguments, and engine.invoke; naming does
not introduce discovery, registration, or a dependency container.
Start with this shape
Section titled “Start with this shape”Use only the directories that the engine needs:
src/ application/ ports.ts capabilities/ classify-ticket.ts domain/ ticket.ts infrastructure/ firecrawl-web-crawler.ts engine.ts direct.ts cli.ts mcp-stdio.ts mcp-http.tstest/ classify-ticket.test.ts firecrawl-web-crawler.test.ts entrypoints.test.tsSmall engines may begin with only src/engine.ts and an entrypoint. Extract a
directory when a real domain rule, port, or implementation needs an independent
owner. Empty architectural folders and pass-through modules add no boundary.
Base filename grammar
Section titled “Base filename grammar”Write TypeScript source filenames in lowercase kebab-case. Name the owned concept or responsibility, then let the directory communicate the layer.
- Use nouns for domain concepts:
ticket.ts,incident-context.ts. - Use verb-object names for actions:
classify-ticket.ts,collect-incident-context.ts. - Add a concrete qualifier before an implemented role:
firecrawl-web-crawler.ts,postgres-ticket-repository.ts,in-memory-ticket-repository.ts. - Reserve a dot suffix for a tool-recognized role such as
<subject>.test.ts. Do not encode architecture with names such asfirecrawl.adapter.tsorticket.service.ts. - Avoid ownerless names such as
utils.ts,helpers.ts,common.ts,service.ts,provider.ts, andtypes.ts. Name the behavior or contract that the module owns.
The filename does not need to repeat its directory. Prefer
capabilities/classify-ticket.ts over
capabilities/classify-ticket-capability.ts.
Name an external provider wrapper
Section titled “Name an external provider wrapper”An outbound provider adapter uses this pattern:
src/infrastructure/<provider>-<port-role>.ts<provider> is the stable product or technology name in kebab-case.
<port-role> is the engine-owned interface that the adapter implements, also
converted to kebab-case. Name the module from the domain-facing port, not from a
provider SDK class or endpoint.
| Engine-owned port | Implementation | Filename |
|---|---|---|
WebCrawler |
Firecrawl | firecrawl-web-crawler.ts |
IssueTracker |
Sentry | sentry-issue-tracker.ts |
TelemetryReader |
New Relic | new-relic-telemetry-reader.ts |
TicketRepository |
PostgreSQL | postgres-ticket-repository.ts |
Match the exported factory to the same pair of names:
export function createFirecrawlWebCrawler( options: FirecrawlWebCrawlerOptions,): WebCrawler { // Translate the Firecrawl protocol into the WebCrawler contract.}This makes replacements legible: FirecrawlWebCrawler and
InternalWebCrawler can both satisfy WebCrawler without changing a
capability.
Use <provider>-<bounded-context>-provider.ts only when one cohesive adapter
intentionally implements several related ports and no single port role names
it accurately. openai-image-provider.ts is an example: the adapter owns one
image-provider boundary while implementing both ImageEditor and
TextImageRenderer. Do not use the broader -provider suffix for a one-port
adapter when a precise role is available.
A provider-neutral mechanism shared by several adapters may use its mechanism
as the name, such as provider-http.ts. It remains inside infrastructure/ and
must not become a provider registry or leak provider payloads into capability
contracts.
Classify a service before naming it
Section titled “Classify a service before naming it”“Service” is not a layer in an Invokta engine. Before creating a service.ts or
services/ directory, classify what the code owns:
| Responsibility | Placement and pattern | Example |
|---|---|---|
| Public use case | src/capabilities/<verb>-<object>.ts |
capabilities/classify-ticket.ts |
| Pure business rule or calculation | src/domain/<concept>.ts or a precise action name |
domain/ticket-priority.ts |
| Reusable application orchestration | src/application/<verb>-<object>.ts |
application/assemble-incident-context.ts |
| Provider or technology integration | src/infrastructure/<provider>-<port-role>.ts |
infrastructure/sentry-issue-tracker.ts |
| Inbound process or protocol host | A named root entrypoint | mcp-http.ts |
Use -service.ts only when “service” is part of the established domain
language and the preceding words still state a specific responsibility. It is
not a default suffix for dependency-injected code. A name such as
ticket-prioritization.ts communicates more than ticket-service.ts.
Common files
Section titled “Common files”| File kind | Path pattern | Rule |
|---|---|---|
| Capability | src/capabilities/<verb>-<object>.ts |
Prefer the verb-object segment of its public capability ID. |
| Shared capability contract | src/capabilities/<concept>-contract.ts |
Use for schemas or types shared by related capabilities. |
| Domain concept | src/domain/<concept>.ts |
Own provider-neutral entities, values, policies, and invariants. |
| Outbound ports | src/application/ports.ts |
Keep a small engine’s interfaces together; split to application/<port-role>.ts when they acquire separate owners. |
| Application operation | src/application/<verb>-<object>.ts |
Use only for orchestration reused outside one capability. |
| Provider adapter | src/infrastructure/<provider>-<port-role>.ts |
Pair the implementation qualifier with the engine-owned port role. |
| Local or technology adapter | src/infrastructure/<technology-or-strategy>-<port-role>.ts |
Examples include file-agent-session-store.ts and in-memory-ticket-repository.ts. |
| Engine factory | src/engine.ts |
Construct dependencies and call createEngine; start no transport here. |
| Capability composition | src/capabilities.ts |
Use when imported and local capability maps need a separate, side-effect-free composition module. |
| Inbound entrypoint | src/direct.ts, src/cli.ts, src/mcp-stdio.ts, or src/mcp-http.ts |
Keep each host thin and route execution through engine.invoke. |
| Focused configuration | src/<concern>-config.ts |
Name the exact concern; keep provider credentials at the composition boundary. |
| Unit or integration test | test/<subject>.test.ts |
Mirror the source subject; use entrypoints.test.ts for execution-channel parity. |
| Protocol test double | test/<provider>-stub.ts |
Emulate the provider boundary without calling the public service. |
Keep a schema beside its owner. A capability input or output schema belongs in the capability module until several related capabilities share it. A persisted domain record schema belongs with the application or domain concept that defines that record. A provider response validator stays inside the provider adapter because it is not a public engine contract.
Split by responsibility, not file size
Section titled “Split by responsibility, not file size”Keep parsing, response validation, error translation, and request construction in one provider adapter while they change together. Split a module only when a part has a distinct owner or is reused by another adapter. When an integration does need several files, retain the same grammar inside a provider directory:
src/infrastructure/firecrawl/ firecrawl-web-crawler.ts firecrawl-client.ts firecrawl-contract.tsHere firecrawl-client.ts is a low-level protocol client used by the adapter;
capabilities still depend only on WebCrawler. Do not inject the provider
client directly into a capability or treat the directory as a runtime plugin.
Check a proposed name
Section titled “Check a proposed name”Before adding a file, ask:
- Does the directory identify the layer that owns this code?
- Does the filename identify a business concept, action, port role, or concrete implementation?
- For an outbound adapter, can a reader see both the provider or technology and the engine-owned role?
- Would replacing the provider leave the port and capability names unchanged?
- Is a generic word such as
service,provider,client, orutilshiding a more precise responsibility?
See Wrap an external provider for a complete implementation and Inject a dependency for provider-neutral ports and composition-root wiring.