@ankhorage/devtools 1.14.3 → 1.14.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,5 +2,6 @@ export declare const agentsManagedFiles: readonly [{
2
2
  readonly relativePath: "AGENTS.md";
3
3
  readonly render: typeof renderAgentsFile;
4
4
  }];
5
+ /*** Render mandatory repository instructions from the target package identity. */
5
6
  declare function renderAgentsFile(targetDirectory: string): Promise<string>;
6
7
  export {};
@@ -6,6 +6,7 @@ export const agentsManagedFiles = [
6
6
  render: renderAgentsFile,
7
7
  },
8
8
  ];
9
+ /*** Render mandatory repository instructions from the target package identity. */
9
10
  async function renderAgentsFile(targetDirectory) {
10
11
  const manifest = await readPackageManifest(targetDirectory);
11
12
  const packageName = readNonEmptyString(manifest.name) ?? 'Package name not declared';
@@ -33,11 +34,25 @@ published public APIs and declared dependencies, never sibling source files.
33
34
  Current-runtime error handling and canonical database or infrastructure migrations remain valid
34
35
  when they support states that the current architecture can intentionally produce.
35
36
 
36
- ## Project structure
37
+ ## Required repository instructions
37
38
 
38
- For directory ownership, package boundaries, architectural profiles, ports and adapters, public
39
- entrypoints, or cross-repository structural work, load and follow
40
- \`.agents/skills/ankhorage-project-structure/SKILL.md\`.
39
+ Before changing any file, read this \`AGENTS.md\` completely and inspect \`.agents/skills/\`.
40
+ For every change, load and follow both baseline repository skills before making changes:
41
+
42
+ 1. \`.agents/skills/ankhorage-coding-rules/SKILL.md\`
43
+ 2. \`.agents/skills/ankhorage-project-structure/SKILL.md\`
44
+
45
+ Load and follow every additional repository-local skill relevant to the task, including skills
46
+ required by either baseline skill. Continue to follow these instructions and skills through
47
+ validation and delivery; do not substitute remembered, globally installed, or generic guidance for
48
+ the repository-local versions.
49
+
50
+ ## Documentation
51
+
52
+ \`README.md\` and the \`paradox/\` documentation tree are generated by \`@ankhorage/paradox\`.
53
+ Never edit generated documentation directly. Update the owning Paradox \`/*** ... */\` comments in
54
+ \`src\`, including \`@readme\` tags where the content belongs in the README, then run \`bun docs\`
55
+ and commit the regenerated artifacts.
41
56
 
42
57
  ## Pull requests
43
58
 
@@ -38,8 +38,10 @@ exceptions or replaced by generic preferences from this skill.
38
38
  validation pass.
39
39
  - Resolve size, complexity, and related findings around cohesive responsibilities and clear data
40
40
  flow. Do not split a function or file mechanically just to cross a threshold.
41
- - Prefer reusable pure transformations, immutable values, and explicit inputs and outputs. Keep
42
- unavoidable mutation and external side effects at clear boundaries.
41
+ - Use functional programming by default: declare bindings with `const`, not `let`; write pure
42
+ functions with explicit inputs and outputs; and use immutable data and updates. Keep side effects
43
+ explicit and at system boundaries. Mutation or reassignment is allowed only for a clearly
44
+ justified boundary or demonstrated performance-critical path, and must remain locally contained.
43
45
  - Keep one canonical implementation. Do not add compatibility aliases, dual paths, historical-state
44
46
  fallbacks, or sibling-source imports.
45
47
 
@@ -1,41 +1,45 @@
1
1
  ---
2
2
  name: ankhorage-project-structure
3
3
  description: >
4
- Define, review, or implement the standard source structure of Ankhorage repositories. Use for
5
- feature ownership, CLI layout, hexagonal boundaries, source-module naming, type ownership,
6
- utilities, or package entrypoints.
4
+ Define, review, or implement the standard source structure of Ankhorage repositories. Use for feature ownership, CLI layout, hexagonal boundaries, source-module naming, Contracts ownership, type ownership, utilities, or package entrypoints.
7
5
  ---
8
6
 
9
7
  # Ankhorage Project Structure
10
8
 
11
- Every Ankhorage repository follows this structure. It applies now to `ankhorage/studio`,
12
- `ankhorage/deploy`, `ankhorage/infra`, `ankhorage/repository`, and `ankhorage/navigator`.
9
+ ## Applicability
13
10
 
14
- ## Required skills
11
+ This skill applies to every Ankhorage repository.
15
12
 
16
- Before structural work, read the repository `AGENTS.md`, inspect its source tree and public
17
- exports, then load both required repository skills:
13
+ ### Contracts repository profile
18
14
 
19
- 1. `.agents/skills/ankhorage-coding-rules/SKILL.md`
20
- 2. [Hexagonal Architecture](../hexagonal-architecture/SKILL.md)
15
+ If the current repository is `ankhorage/contracts`, apply only this profile:
21
16
 
22
- If `ankhorage-coding-rules` is missing or unreadable, stop immediately and report exactly:
17
+ - The repository may contain only portable, serializable contracts expressed as interfaces and
18
+ types. Every field must be serializable and reconstructable without executable behavior or
19
+ repository-local runtime objects. Do not add call signatures, function-valued properties,
20
+ functions, classes, constants, enums, mutable state, adapters, framework objects, or
21
+ implementation helpers.
22
+ - A contract belongs in this repository only when it is required across multiple repositories. A
23
+ current or coordinated change MUST identify at least two consuming repositories. Keep
24
+ single-repository types with their owning repository; anticipated reuse alone is not sufficient.
25
+ - Consumers import contracts through published public subpaths and declared dependencies, never
26
+ sibling source files or duplicated local declarations.
23
27
 
24
- ```
25
- Cannot continue: the required repository skill `ankhorage-coding-rules` is missing or unreadable at `.agents/skills/ankhorage-coding-rules/SKILL.md`. Synchronize the repository skills from `@ankhorage/devtools` and retry.
26
- ```
28
+ After enforcing this profile, stop before the remaining source-layout, implementation, utility,
29
+ and migration rules; they do not apply to `ankhorage/contracts`.
27
30
 
28
- If `hexagonal-architecture` is missing or unreadable, stop immediately and report exactly:
31
+ ## Required skills
29
32
 
30
- ```
31
- Cannot continue: the required repository skill `hexagonal-architecture` is missing or unreadable at `.agents/skills/hexagonal-architecture/SKILL.md`. Synchronize the repository skills from `@ankhorage/devtools` and retry.
32
- ```
33
+ Before structural work outside `ankhorage/contracts`, read the repository `AGENTS.md`, inspect its
34
+ source tree and public exports, then load the required Hexagonal Architecture skill from the
35
+ repository root. Do not resolve it relative to this skill's own installation location:
36
+
37
+ 1. `<repo-root>/.agents/skills/hexagonal-architecture/SKILL.md`
33
38
 
34
- ## Repository-root examples
39
+ ## Required source layout
35
40
 
36
- `examples/` is a generally valid repository-root folder in every repository covered by this skill.
37
- Use it for complete, intentional, user-facing examples that people can inspect, copy, install, and
38
- run independently of a monorepo or internal fixture layout.
41
+ - `examples/`: Repository-root folder in this standalone repository;
42
+ Use it for complete, intentional, user-facing examples that people can inspect, copy, install, and run independently of a monorepo or internal fixture layout.
39
43
 
40
44
  Each example lives in a named subdirectory, such as `examples/basic-usage/*.ts`. Do not put example
41
45
  source files directly under `examples/`.
@@ -43,17 +47,29 @@ source files directly under `examples/`.
43
47
  Test-only fixtures remain owned by the applicable test structure. Do not relabel fixtures as public
44
48
  examples merely to bypass repository structure rules.
45
49
 
46
- ## Required source layout
50
+ - `src/cli/` must exist or have a concrete issue tracking the missing CLI commands;
51
+ CLI modules are thin inbound adapters: they parse input, invoke a feature use case, and render
52
+ output.
47
53
 
48
- Every repository provides `src/features/`. It lists the repository's actual product capabilities;
49
- technical categories are not features. Each feature owns its own hexagonal structure as needed,
50
- following the required Hexagonal Architecture skill. Do not create empty layers.
54
+ The filesystem below `src/cli/commands/` mirrors the public command path after the package prefix:
55
+
56
+ ```text
57
+ ankh <package> <segment> ... <command>
58
+ -> src/cli/commands/<segment>/.../<command>.ts
59
+ ```
60
+
61
+ The package prefix is represented by the provider and is not repeated under `commands/`. Flags and
62
+ positional arguments do not affect this directory tree. Each command file follows the one-export
63
+ rule: `commands/projects/list.ts` exports `list` and owns only the command-specific input/output
64
+ mapping.
51
65
 
52
- Every repository provides `src/cli/`, or has a concrete issue tracking the missing CLI commands.
53
- CLI modules are thin inbound adapters: they parse input, invoke a feature use case, and render
54
- output.
66
+ - `src/features/`: Lists the repository's actual product capabilities;
67
+ Technical categories are not features. Each feature owns its own hexagonal structure as needed,
68
+ following the required Hexagonal Architecture skill. Do not create empty layers.
55
69
 
56
70
  ```text
71
+ examples/
72
+ <example>/
57
73
  src/
58
74
  cli/
59
75
  createCliProvider.ts
@@ -73,42 +89,33 @@ src/
73
89
  inbound/
74
90
  outbound/
75
91
  composition/
92
+ constants/
93
+ <topic>.ts
76
94
  utils/
77
95
  types/
78
96
  <topic>.ts
79
- constants.ts
97
+ constants/
98
+ <topic>.ts
80
99
  utils/
81
100
  ```
82
101
 
83
102
  Keep only deliberate package facades directly under `src/`. Public package subpaths must name their
84
103
  explicit module in `package.json`; generic `index.ts` barrels are not public API exceptions.
85
104
 
86
- The filesystem below `src/cli/commands/` mirrors the public command path after the package prefix:
87
-
88
- ```text
89
- ankh <package> <segment> ... <command>
90
- -> src/cli/commands/<segment>/.../<command>.ts
91
- ```
92
-
93
- The package prefix is represented by the provider and is not repeated under `commands/`. Flags and
94
- positional arguments do not affect this directory tree. Each command file follows the one-export
95
- rule: `commands/projects/list.ts` exports `list` and owns only the command-specific input/output
96
- mapping.
97
-
98
- ## Feature taxonomy
105
+ ## General Taxonomy
99
106
 
100
107
  Siblings always represent the same kind of entity. A folder cannot be an unrelated catch-all beside
101
- peer entities. For example, this is invalid because `otherFolder` is not a color:
108
+ peer entities. For example, this is invalid because `other/` is not a color:
102
109
 
103
110
  ```text
104
111
  colors/
105
112
  red/
106
113
  green/
107
114
  blue/
108
- otherFolder/
115
+ other/
109
116
  ```
110
117
 
111
- Resolve the ownership of `otherFolder` and move it to the appropriate taxonomy. Use domain names for
118
+ Resolve the ownership of `other` and move it to the appropriate taxonomy. Use domain names for
112
119
  features, not framework, transport, database, or generic technical names.
113
120
 
114
121
  ## Implementation modules
@@ -121,8 +128,7 @@ convenience barrels and must not expose private implementation details.
121
128
 
122
129
  - `myFunction.ts` exports `myFunction`.
123
130
  - `myFunctionAsync.ts` exports `myFunctionAsync`.
124
- - A public operation that is asynchronous or returns a `Promise` uses the `Async` suffix in both its
125
- filename and exported name.
131
+ - A public operation that is asynchronous or returns a `Promise` uses the `Async` suffix in both its filename and exported name.
126
132
 
127
133
  Keep private helpers below that exported declaration when they are used only by that module.
128
134
  Decide the owner of a reused function using the utility rules below, before creating another file.
@@ -132,17 +138,9 @@ Decide the owner of a reused function using the utility rules below, before crea
132
138
  Choose type ownership by its production consumers, not by the number of textual references or
133
139
  whether a barrel happens to re-export it:
134
140
 
135
- 1. **Used by one implementation module:** keep the type directly below the function that owns it,
136
- without `export`. Its private helpers can use the same local type. A test does not justify
137
- exporting an implementation-private type; test through the function boundary.
138
- 2. **Reused within the repository:** put related types together in `src/types/<topic>.ts` and use
139
- type-only imports. Name the file for a cohesive topic, not for each individual type. Such a file
140
- may export multiple related types/interfaces and contains no runtime implementation. Do not mix
141
- type-only files among feature functions or `utils/`, and do not create one global catch-all file.
142
- 3. **Shared across repositories:** the canonical declaration belongs in `@ankhorage/contracts` at
143
- the owning topic's public subpath. Consumers import that contract through a declared dependency,
144
- not another repository's source or a duplicated local declaration. Keep framework-specific
145
- adapters separate from the portable shared contract.
141
+ 1. **Used by one implementation module:** keep the type directly below the function that owns it, without `export`. Its private helpers can use the same local type. A test does not justify exporting an implementation-private type; test through the function boundary.
142
+ 2. **Reused within the repository:** put related types together in `src/types/<topic>.ts` and use type-only imports. Name the file for a cohesive topic, not for each individual type. Such a file may export multiple related types/interfaces and contains no runtime implementation. Do not mix type-only files among feature functions or `utils/`, and do not create one global catch-all file.
143
+ 3. **Shared across repositories and serializable:** when at least two repositories require the same portable data declaration, it belongs in `@ankhorage/contracts` at the owning topic's public subpath. Consumers import that contract through a declared dependency, not another repository's source or a duplicated local declaration. Keep non-serializable API types with their implementation-owning package and consume them through that package's public API. Keep framework-specific adapters separate from the portable shared contract.
146
144
 
147
145
  Inspect published API declarations and real consumer imports before privatizing or relocating a
148
146
  type. A public boundary type is not private just because only one implementation uses it locally.
@@ -161,11 +159,10 @@ Constants are static declarations, not utility implementations. Do not create on
161
159
  constant per constant-named file under `utils/`.
162
160
 
163
161
  1. **Used by one implementation module:** keep the constant private in the module that owns it.
164
- 2. **Reused only inside a feature:** group related constants in that feature's `constants.ts`.
165
- 3. **Shared across features in one package:** group related package metadata, static policy values,
166
- and other constants in `src/constants.ts`.
162
+ 2. **Reused only inside a feature:** group related constants in that feature's `constants/<topic>.ts`.
163
+ 3. **Shared across features in one package:** group related package metadata, static policy values, and other constants in `src/constants/<topic>.ts`.
167
164
 
168
- A `constants.ts` module may export multiple related constants. Keep it cohesive by ownership and
165
+ A `constants/<topic>.ts` module may export multiple related constants. Keep it cohesive by ownership and
169
166
  purpose; it is not a package-wide catch-all. Split constants when they have different owners, not
170
167
  merely to create one file per export.
171
168
 
@@ -173,27 +170,34 @@ For example, Navigator's
173
170
  [`src/utils/NAVIGATOR_PACKAGE_METADATA.ts`](https://github.com/ankhorage/navigator/blob/main/src/utils/NAVIGATOR_PACKAGE_METADATA.ts)
174
171
  and
175
172
  [`src/utils/NAVIGATOR_ROUTER_POLICY.ts`](https://github.com/ankhorage/navigator/blob/main/src/utils/NAVIGATOR_ROUTER_POLICY.ts)
176
- belong together in `ankhorage/navigator/src/constants.ts`.
173
+ belong together in `ankhorage/navigator/src/constants/navigator.ts`.
177
174
 
178
175
  ## Utilities
179
176
 
180
177
  `utils/` is the only utility directory name. Do not create `shared/`, `helper/`, `helpers/`,
181
178
  `common/`, or equivalent catch-all folders. It is not a destination for every pure function or type.
182
179
 
180
+ Apply **reuse before implementation** and **shared by default** before choosing a local owner. For
181
+ every function that could reasonably be reused across repositories, you MUST first inspect the
182
+ published `@ankhorage/utility` public API and its owning topic. Reuse the existing export when its
183
+ semantics match. If the function is missing and is generic without product, manifest, or framework
184
+ policy, implement, test, and export it from the appropriate Utility topic, then consume that public
185
+ export through a declared dependency. Do not duplicate it locally or add a forwarding wrapper.
186
+ `isRecord` from `@ankhorage/utility/object` is one motivating example of this general rule, not a
187
+ special case.
188
+
183
189
  - Used by one module: keep the helper private below its owning function.
184
190
  - Reused only inside a feature: keep it in that feature's `utils/`.
185
191
  - Shared across features but tied to this package's capability or policy: use `src/utils/`.
186
192
  Navigator topology traversal or Expo Router-specific validation does not become a general utility
187
193
  merely because several navigator features use it.
188
- - Generally reusable without the owning product, manifest, or framework policy: inspect the
189
- published `@ankhorage/utility` API first, reuse it where semantics match, and put missing general
190
- helpers in that package's owning topic. Examples include generic string escaping or source-literal
191
- serialization. Do not copy a utility locally, create a forwarding wrapper, or change semantics
192
- just to reuse a similarly named function.
194
+ - Generally reusable across repositories: it belongs in the canonical `@ankhorage/utility` topic
195
+ under the reuse-first rule above. Examples include generic string escaping or source-literal
196
+ serialization. Do not change semantics merely to reuse a similarly named function.
193
197
 
194
198
  Separate the decisions for functions and types: reusable functions belong to Utility when general;
195
199
  repo-local type groups belong to `src/types/`; repo-crossing types belong to Contracts. Respect
196
200
  release boundaries and obtain approval for additional package changes when they exceed the task.
197
201
 
198
202
  This skill defines the target architecture. Schedule repository migrations separately and in this
199
- order: Studio, Deploy, Infra, Repository, Navigator.
203
+ order: Studio, Deploy, Infra, Repository, Navigator, Surface, ZORA.
@@ -1,72 +1,276 @@
1
1
  ---
2
2
  name: hexagonal-architecture
3
- description: >
4
- Organize Ankhorage features with ports and adapters so domain policy stays independent from
5
- transport, frameworks, and infrastructure.
6
- origin: ECC, adapted for Ankhorage
7
- metadata:
8
- github-repo: https://github.com/affaan-m/ECC
9
- github-ref: refs/tags/v2.2.0
10
- github-path: docs/ja-JP/skills/hexagonal-architecture
3
+ description: ヘキサゴナルアーキテクチャ(ポート・アダプタパターン)、境界の分離、および外部依存関係の管理。
4
+ origin: ECC
11
5
  ---
12
6
 
13
7
  # Hexagonal Architecture
14
8
 
15
- Hexagonal architecture keeps a feature's domain policy independent from its transport, framework,
16
- and infrastructure details. The application layer depends on ports; inbound and outbound adapters
17
- implement the edge-specific work.
9
+ Hexagonal architecture (Ports and Adapters) keeps business logic independent from frameworks, transport, and persistence details. The core app depends on abstract ports, and adapters implement those ports at the edges.
18
10
 
19
- ## Feature boundaries
11
+ ## When to Use
20
12
 
21
- Apply this structure inside the owning `src/features/<feature>/` directory. A feature may contain
22
- only the layers it needs:
13
+ - Building new features where long-term maintainability and testability matter.
14
+ - Refactoring layered or framework-heavy code where domain logic is mixed with I/O concerns.
15
+ - Supporting multiple interfaces for the same use case (HTTP, CLI, queue workers, cron jobs).
16
+ - Replacing infrastructure (database, external APIs, message bus) without rewriting business rules.
17
+
18
+ Use this skill when the request involves boundaries, domain-centric design, refactoring tightly coupled services, or decoupling application logic from specific libraries.
19
+
20
+ ## Core Concepts
21
+
22
+ - **Domain model**: Business rules and entities/value objects. No framework imports.
23
+ - **Use cases (application layer)**: Orchestrate domain behavior and workflow steps.
24
+ - **Inbound ports**: Contracts describing what the application can do (commands/queries/use-case interfaces).
25
+ - **Outbound ports**: Contracts for dependencies the application needs (repositories, gateways, event publishers, clock, UUID, etc.).
26
+ - **Adapters**: Infrastructure and delivery implementations of ports (HTTP controllers, DB repositories, queue consumers, SDK wrappers).
27
+ - **Composition root**: Single wiring location where concrete adapters are bound to use cases.
28
+
29
+ Outbound port interfaces usually live in the application layer (or in domain only when the abstraction is truly domain-level), while infrastructure adapters implement them.
30
+
31
+ Dependency direction is always inward:
32
+
33
+ - Adapters -> application/domain
34
+ - Application -> port interfaces (inbound/outbound contracts)
35
+ - Domain -> domain-only abstractions (no framework or infrastructure dependencies)
36
+ - Domain -> nothing external
37
+
38
+ ## How It Works
39
+
40
+ ### Step 1: Model a use case boundary
41
+
42
+ Define a single use case with a clear input and output DTO. Keep transport details (Express `req`, GraphQL `context`, job payload wrappers) outside this boundary.
43
+
44
+ ### Step 2: Define outbound ports first
45
+
46
+ Identify every side effect as a port:
47
+
48
+ - persistence (`UserRepositoryPort`)
49
+ - external calls (`BillingGatewayPort`)
50
+ - cross-cutting (`LoggerPort`, `ClockPort`)
51
+
52
+ Ports should model capabilities, not technologies.
53
+
54
+ ### Step 3: Implement the use case with pure orchestration
55
+
56
+ Use case class/function receives ports via constructor/arguments. It validates application-level invariants, coordinates domain rules, and returns plain data structures.
57
+
58
+ ### Step 4: Build adapters at the edge
59
+
60
+ - Inbound adapter converts protocol input to use-case input.
61
+ - Outbound adapter maps app contracts to concrete APIs/ORM/query builders.
62
+ - Mapping stays in adapters, not inside use cases.
63
+
64
+ ### Step 5: Wire everything in a composition root
65
+
66
+ Instantiate adapters, then inject them into use cases. Keep this wiring centralized to avoid hidden service-locator behavior.
67
+
68
+ ### Step 6: Test per boundary
69
+
70
+ - Unit test use cases with fake ports.
71
+ - Integration test adapters with real infra dependencies.
72
+ - E2E test user-facing flows through inbound adapters.
73
+
74
+ ## Architecture Diagram
75
+
76
+ ```mermaid
77
+ flowchart LR
78
+ Client["Client (HTTP/CLI/Worker)"] --> InboundAdapter["Inbound Adapter"]
79
+ InboundAdapter -->|"calls"| UseCase["UseCase (Application Layer)"]
80
+ UseCase -->|"uses"| OutboundPort["OutboundPort (Interface)"]
81
+ OutboundAdapter["Outbound Adapter"] -->|"implements"| OutboundPort
82
+ OutboundAdapter --> ExternalSystem["DB/API/Queue"]
83
+ UseCase --> DomainModel["DomainModel"]
84
+ ```
85
+
86
+ ## Suggested Module Layout
87
+
88
+ Use feature-first organization with explicit boundaries:
23
89
 
24
90
  ```text
25
- <feature>/
26
- domain/
27
- application/
28
- ports/
29
- inbound/
30
- outbound/
31
- use-cases/
32
- adapters/
33
- inbound/
34
- outbound/
35
- composition/
36
- utils/
91
+ src/
92
+ features/
93
+ orders/
94
+ domain/
95
+ Order.ts
96
+ OrderPolicy.ts
97
+ application/
98
+ ports/
99
+ inbound/
100
+ CreateOrder.ts
101
+ outbound/
102
+ OrderRepositoryPort.ts
103
+ PaymentGatewayPort.ts
104
+ use-cases/
105
+ CreateOrderUseCase.ts
106
+ adapters/
107
+ inbound/
108
+ http/
109
+ createOrderRoute.ts
110
+ outbound/
111
+ postgres/
112
+ PostgresOrderRepository.ts
113
+ stripe/
114
+ StripePaymentGateway.ts
115
+ composition/
116
+ ordersContainer.ts
37
117
  ```
38
118
 
39
- - `domain/` contains entities, value objects, and business policy. It imports no framework,
40
- transport, database, or SDK code.
41
- - `application/` defines use cases and the ports they require. It orchestrates domain behavior and
42
- depends only inward.
43
- - `adapters/inbound/` converts CLI, HTTP, worker, or UI input to use-case input.
44
- - `adapters/outbound/` implements application ports for persistence, APIs, queues, clocks, and
45
- other external dependencies.
46
- - `composition/` explicitly wires concrete adapters to use cases.
119
+ ## TypeScript Example
120
+
121
+ ### Port definitions
122
+
123
+ ```typescript
124
+ export interface OrderRepositoryPort {
125
+ save(order: Order): Promise<void>;
126
+ findById(orderId: string): Promise<Order | null>;
127
+ }
128
+
129
+ export interface PaymentGatewayPort {
130
+ authorize(input: { orderId: string; amountCents: number }): Promise<{ authorizationId: string }>;
131
+ }
132
+ ```
133
+
134
+ ### Use case
135
+
136
+ ```typescript
137
+ type CreateOrderInput = {
138
+ orderId: string;
139
+ amountCents: number;
140
+ };
141
+
142
+ type CreateOrderOutput = {
143
+ orderId: string;
144
+ authorizationId: string;
145
+ };
146
+
147
+ export class CreateOrderUseCase {
148
+ constructor(
149
+ private readonly orderRepository: OrderRepositoryPort,
150
+ private readonly paymentGateway: PaymentGatewayPort,
151
+ ) {}
152
+
153
+ async execute(input: CreateOrderInput): Promise<CreateOrderOutput> {
154
+ const order = Order.create({ id: input.orderId, amountCents: input.amountCents });
155
+
156
+ const auth = await this.paymentGateway.authorize({
157
+ orderId: order.id,
158
+ amountCents: order.amountCents,
159
+ });
160
+
161
+ // markAuthorized returns a new Order instance; it does not mutate in place.
162
+ const authorizedOrder = order.markAuthorized(auth.authorizationId);
163
+ await this.orderRepository.save(authorizedOrder);
164
+
165
+ return {
166
+ orderId: order.id,
167
+ authorizationId: auth.authorizationId,
168
+ };
169
+ }
170
+ }
171
+ ```
172
+
173
+ ### Outbound adapter
174
+
175
+ ```typescript
176
+ export class PostgresOrderRepository implements OrderRepositoryPort {
177
+ constructor(private readonly db: SqlClient) {}
178
+
179
+ async save(order: Order): Promise<void> {
180
+ await this.db.query(
181
+ 'insert into orders (id, amount_cents, status, authorization_id) values ($1, $2, $3, $4)',
182
+ [order.id, order.amountCents, order.status, order.authorizationId],
183
+ );
184
+ }
185
+
186
+ async findById(orderId: string): Promise<Order | null> {
187
+ const row = await this.db.oneOrNone('select * from orders where id = $1', [orderId]);
188
+ return row ? Order.rehydrate(row) : null;
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### Composition root
194
+
195
+ ```typescript
196
+ export const buildCreateOrderUseCase = (deps: { db: SqlClient; stripe: StripeClient }) => {
197
+ const orderRepository = new PostgresOrderRepository(deps.db);
198
+ const paymentGateway = new StripePaymentGateway(deps.stripe);
199
+
200
+ return new CreateOrderUseCase(orderRepository, paymentGateway);
201
+ };
202
+ ```
203
+
204
+ ## Multi-Language Mapping
205
+
206
+ Use the same boundary rules across ecosystems; only syntax and wiring style change.
207
+
208
+ - **TypeScript/JavaScript**
209
+ - Ports: `application/ports/*` as interfaces/types.
210
+ - Use cases: classes/functions with constructor/argument injection.
211
+ - Adapters: `adapters/inbound/*`, `adapters/outbound/*`.
212
+ - Composition: explicit factory/container module (no hidden globals).
213
+ - **Java**
214
+ - Packages: `domain`, `application.port.in`, `application.port.out`, `application.usecase`, `adapter.in`, `adapter.out`.
215
+ - Ports: interfaces in `application.port.*`.
216
+ - Use cases: plain classes (Spring `@Service` is optional, not required).
217
+ - Composition: Spring config or manual wiring class; keep wiring out of domain/use-case classes.
218
+ - **Kotlin**
219
+ - Modules/packages mirror the Java split (`domain`, `application.port`, `application.usecase`, `adapter`).
220
+ - Ports: Kotlin interfaces.
221
+ - Use cases: classes with constructor injection (Koin/Dagger/Spring/manual).
222
+ - Composition: module definitions or dedicated composition functions; avoid service locator patterns.
223
+ - **Go**
224
+ - Packages: `internal/<feature>/domain`, `application`, `ports`, `adapters/inbound`, `adapters/outbound`.
225
+ - Ports: small interfaces owned by the consuming application package.
226
+ - Use cases: structs with interface fields plus explicit `New...` constructors.
227
+ - Composition: wire in `cmd/<app>/main.go` (or dedicated wiring package), keep constructors explicit.
228
+
229
+ ## Anti-Patterns to Avoid
230
+
231
+ - Domain entities importing ORM models, web framework types, or SDK clients.
232
+ - Use cases reading directly from `req`, `res`, or queue metadata.
233
+ - Returning database rows directly from use cases without domain/application mapping.
234
+ - Letting adapters call each other directly instead of flowing through use-case ports.
235
+ - Spreading dependency wiring across many files with hidden global singletons.
47
236
 
48
- Dependency direction is always inward: adapters depend on application and domain; application
49
- depends on domain and ports; domain has no external dependencies. Adapters do not call each other
50
- to bypass a use case.
237
+ ## Migration Playbook
51
238
 
52
- ## Ports and use cases
239
+ 1. Pick one vertical slice (single endpoint/job) with frequent change pain.
240
+ 2. Extract a use-case boundary with explicit input/output types.
241
+ 3. Introduce outbound ports around existing infrastructure calls.
242
+ 4. Move orchestration logic from controllers/services into the use case.
243
+ 5. Keep old adapters, but make them delegate to the new use case.
244
+ 6. Add tests around the new boundary (unit + adapter integration).
245
+ 7. Repeat slice-by-slice; avoid full rewrites.
53
246
 
54
- Define an inbound port when a feature needs a stable use-case boundary. Define an outbound port for
55
- each capability the application needs from persistence, an external system, or cross-cutting runtime
56
- services. Model ports as capabilities rather than technologies.
247
+ ### Refactoring Existing Systems
57
248
 
58
- Use cases accept plain inputs, enforce application invariants, coordinate domain behavior through
59
- ports, and return plain outputs. Keep protocol request objects, database rows, SDK clients, and
60
- transport error formats in adapters.
249
+ - **Strangler approach**: keep current endpoints, route one use case at a time through new ports/adapters.
250
+ - **No big-bang rewrites**: migrate per feature slice and preserve behavior with characterization tests.
251
+ - **Facade first**: wrap legacy services behind outbound ports before replacing internals.
252
+ - **Composition freeze**: centralize wiring early so new dependencies do not leak into domain/use-case layers.
253
+ - **Slice selection rule**: prioritize high-churn, low-blast-radius flows first.
254
+ - **Rollback path**: keep a reversible toggle or route switch per migrated slice until production behavior is verified.
61
255
 
62
- ## Testing
256
+ ## Testing Guidance (Same Hexagonal Boundaries)
63
257
 
64
- Test domain rules without framework setup. Test use cases with fakes for outbound ports. Test
65
- adapters at their boundary with the real protocol or infrastructure they translate. Cover critical
66
- flows end-to-end through an inbound adapter, use case, and outbound adapter.
258
+ - **Domain tests**: test entities/value objects as pure business rules (no mocks, no framework setup).
259
+ - **Use-case unit tests**: test orchestration with fakes/stubs for outbound ports; assert business outcomes and port interactions.
260
+ - **Outbound adapter contract tests**: define shared contract suites at port level and run them against each adapter implementation.
261
+ - **Inbound adapter tests**: verify protocol mapping (HTTP/CLI/queue payload to use-case input and output/error mapping back to protocol).
262
+ - **Adapter integration tests**: run against real infrastructure (DB/API/queue) for serialization, schema/query behavior, retries, and timeouts.
263
+ - **End-to-end tests**: cover critical user journeys through inbound adapter -> use case -> outbound adapter.
264
+ - **Refactor safety**: add characterization tests before extraction; keep them until new boundary behavior is stable and equivalent.
67
265
 
68
- ## Ankhorage conventions
266
+ ## Best Practices Checklist
69
267
 
70
- Follow `ankhorage-project-structure` for feature naming, one-export source modules, and `utils/`
71
- ownership. Do not introduce legacy paths, compatibility facades, dual implementations, or rollback
72
- switches while applying this architecture; use the current architecture directly.
268
+ - Domain and use-case layers import only internal types and ports.
269
+ - Every external dependency is represented by an outbound port.
270
+ - Validation occurs at boundaries (inbound adapter + use-case invariants).
271
+ - Use immutable transformations (return new values/entities instead of mutating shared state).
272
+ - Errors are translated across boundaries (infra errors -> application/domain errors).
273
+ - Composition root is explicit and easy to audit.
274
+ - Use cases are testable with simple in-memory fakes for ports.
275
+ - Refactoring starts from one vertical slice with behavior-preserving tests.
276
+ - Language/framework specifics stay in adapters, never in domain rules.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.14.3",
3
+ "version": "1.14.5",
4
4
  "description": "Shared development tools and repository standards for Ankhorage",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ankhorage/devtools#readme",