@drunkcoding/dknet-implementation-skills 14.1.0 → 14.1.1

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.
Files changed (35) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +37 -26
  3. package/package.json +2 -3
  4. package/{.claude-plugin → plugin/.claude-plugin}/plugin.json +1 -1
  5. package/{agents → plugin/agents}/dknet-architect.md +4 -4
  6. package/{agents → plugin/agents}/dknet-bdd-engineer.md +6 -6
  7. package/{agents → plugin/agents}/dknet-implementer.md +11 -11
  8. package/{skills → plugin/skills}/README.md +13 -12
  9. package/{skills → plugin/skills}/dknet-auth-and-ownership/SKILL.md +100 -36
  10. package/{skills → plugin/skills}/dknet-bdd-tests/SKILL.md +25 -16
  11. package/{skills → plugin/skills}/dknet-bdd-tests/checklist.md +1 -1
  12. package/{skills → plugin/skills}/dknet-crud/SKILL.md +34 -22
  13. package/{skills → plugin/skills}/dknet-ddd-principles/SKILL.md +62 -13
  14. package/{skills → plugin/skills}/dknet-docs/SKILL.md +73 -31
  15. package/{skills → plugin/skills}/dknet-docs/templates/README-template.md +8 -8
  16. package/{skills → plugin/skills}/dknet-docs/templates/api-reference-template.md +7 -0
  17. package/{skills → plugin/skills}/dknet-docs/templates/architecture-template.md +14 -8
  18. package/{skills → plugin/skills}/dknet-docs/templates/data-model-template.md +2 -2
  19. package/{skills → plugin/skills}/dknet-docs/templates/events-template.md +2 -2
  20. package/{skills → plugin/skills}/dknet-dto-mapping/SKILL.md +37 -29
  21. package/{skills → plugin/skills}/dknet-efcore-config/SKILL.md +38 -38
  22. package/{skills → plugin/skills}/dknet-endpoint/SKILL.md +70 -43
  23. package/{skills → plugin/skills}/dknet-entity/SKILL.md +43 -33
  24. package/{skills → plugin/skills}/dknet-feature/SKILL.md +34 -13
  25. package/{skills → plugin/skills}/dknet-feature-lifecycle/SKILL.md +48 -42
  26. package/{skills → plugin/skills}/dknet-feature-remove/SKILL.md +15 -15
  27. package/{skills → plugin/skills}/dknet-messaging-events/SKILL.md +36 -29
  28. package/{skills → plugin/skills}/dknet-package-adoption/SKILL.md +3 -3
  29. package/{skills → plugin/skills}/dknet-platform-config/SKILL.md +14 -14
  30. package/{skills → plugin/skills}/dknet-project-structure/SKILL.md +32 -32
  31. package/{skills → plugin/skills}/dknet-queries-specs/SKILL.md +21 -13
  32. package/{skills → plugin/skills}/dknet-scaffold/SKILL.md +3 -2
  33. package/{skills → plugin/skills}/dknet-unit-tests/SKILL.md +27 -22
  34. package/plugin.json +3 -3
  35. /package/{skills → plugin/skills}/dknet-docs/checklist.md +0 -0
@@ -1,5 +1,11 @@
1
1
  # {FeatureName} — Architecture
2
2
 
3
+ > Diagrams below are written as Mermaid so this template stays readable on its own. Render the
4
+ > architecture, sequence, data-flow and lifecycle diagrams with **archify**
5
+ > (<https://github.com/tt-a1i/archify>) and replace each fenced block with an image link to the
6
+ > exported SVG in `diagrams/`, keeping the archify JSON source beside it. Keep `erDiagram` as
7
+ > Mermaid — archify has no table-schema type. See the `dknet-docs` skill, *Diagrams*.
8
+
3
9
  ## Vertical Slice Overview
4
10
 
5
11
  This feature follows the DKNet vertical slice architecture.
@@ -9,11 +15,11 @@ Each layer has a single, focused responsibility for this feature.
9
15
  graph TD
10
16
  Client["Client / Browser"]
11
17
 
12
- subgraph API["Minimal.Api"]
18
+ subgraph API["<YourApp>.Api"]
13
19
  EP["{EntityName}V1Endpoint\n(IEndpointConfig)"]
14
20
  end
15
21
 
16
- subgraph AppServices["Minimal.AppServices"]
22
+ subgraph AppServices["<YourApp>.AppServices"]
17
23
  REQ["Request Types\n(Create / Update / Delete\n+ custom actions)"]
18
24
  VAL["Validators\n(FluentValidation)"]
19
25
  HDL["Command Handlers\n(IHandler)"]
@@ -21,11 +27,11 @@ graph TD
21
27
  EVT["Domain Events\n({EntityName}CreatedEvent etc.)"]
22
28
  end
23
29
 
24
- subgraph Domains["Minimal.Domains"]
30
+ subgraph Domains["<YourApp>.Domains"]
25
31
  ENT["{EntityName}\n(AggregateRoot)"]
26
32
  end
27
33
 
28
- subgraph Infra["Minimal.Infra"]
34
+ subgraph Infra["<YourApp>.Infra"]
29
35
  MAP["{EntityName}Mapper.cs\n(EF Core Config)"]
30
36
  REPO["IRepositorySpec\n(EF Core + Spec)"]
31
37
  EVH["Event Handlers\n(Azure Bus / In-Memory)"]
@@ -160,7 +166,7 @@ graph LR
160
166
 
161
167
  | Layer | Responsibility in this feature |
162
168
  |-------|-------------------------------|
163
- | `Minimal.Api` | Route mapping only; zero business logic |
164
- | `Minimal.AppServices` | Command handling, validation, event publishing |
165
- | `Minimal.Domains` | Entity state, domain rules, invariants |
166
- | `Minimal.Infra` | Persistence, EF Core config, message bus setup |
169
+ | `<YourApp>.Api` | Route mapping only; zero business logic |
170
+ | `<YourApp>.AppServices` | Command handling, validation, event publishing |
171
+ | `<YourApp>.Domains` | Entity state, domain rules, invariants |
172
+ | `<YourApp>.Infra` | Persistence, EF Core config, message bus setup |
@@ -50,7 +50,7 @@ erDiagram
50
50
 
51
51
  ## EF Core Mapping Configuration
52
52
 
53
- Source: `ApiEndpoints/Minimal.Infra/Features/{EntityFolder}/Mappers/{EntityName}Mapper.cs`
53
+ Source: `ApiEndpoints/<YourApp>.Infra/Features/{EntityFolder}/Mappers/{EntityName}Mapper.cs`
54
54
 
55
55
  Key mapping decisions:
56
56
 
@@ -96,4 +96,4 @@ Key mapping decisions:
96
96
  | `Initial_{EntityName}` | Create initial `{EntityTableName}` table |
97
97
  | `Add_{Field}_To_{EntityName}` | {Reason for the change} |
98
98
 
99
- > Keep this table updated when running `dotnet ef migrations add <Name> -c CoreDbContext -p Minimal.Infra/Minimal.Infra.csproj`.
99
+ > Keep this table updated when running `dotnet ef migrations add <Name> -c CoreDbContext -p <YourApp>.Infra/<YourApp>.Infra.csproj`.
@@ -104,14 +104,14 @@ This feature does not currently consume events from other features.
104
104
 
105
105
  ## Event Bus Configuration
106
106
 
107
- Events are dispatched via the Minimal message bus with two bus types:
107
+ Events are dispatched via the solution's SlimMessageBus with two bus types:
108
108
 
109
109
  | Bus Type | When Active | Purpose |
110
110
  |----------|-------------|---------|
111
111
  | **In-Memory** | Always (all environments) | Same-process handlers; local side effects |
112
112
  | **Azure Service Bus** | When `ConnectionStrings:AzureBus` is non-empty | Cross-service/process messaging |
113
113
 
114
- See `ApiEndpoints/Minimal.Infra/Extensions/ServiceBusSetup.cs` for wiring configuration.
114
+ See `ApiEndpoints/<YourApp>.Infra/Extensions/ServiceBusSetup.cs` for wiring configuration.
115
115
 
116
116
  ```mermaid
117
117
  graph LR
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dknet-dto-mapping
3
- description: Design response DTOs and Mapster mapping for a DKNet.Minimal feature — hand-written vs [GenerateDto] shapes, custom Mapster IRegister mappings for values the generator's convention can't produce, LazyMapper, and the JSON/sensitive-data contract. Use after (or alongside) dknet-crud when a handler needs a DTO to return.
3
+ description: Design response DTOs and Mapster mapping for a DKNet.Minimal feature — [GenerateDto] first and hand-written records as the fallback, custom Mapster IRegister mappings for values the generator's convention can't produce, LazyMapper, and the JSON/sensitive-data contract. Use after (or alongside) dknet-crud when a handler needs a DTO to return.
4
4
  ---
5
5
 
6
6
  # DTO and Mapster mapping
@@ -8,27 +8,15 @@ description: Design response DTOs and Mapster mapping for a DKNet.Minimal featur
8
8
  Response DTO shape and how it gets filled from the entity. For request contracts, validators and
9
9
  handlers, load `dknet-crud`. For paged query projections, load `dknet-queries-specs`.
10
10
 
11
- ## Two DTO shapes
11
+ ## Two DTO shapes — `[GenerateDto]` first
12
12
 
13
- **Hand-written** — a plain record, exactly the fields you list:
14
-
15
- ```csharp
16
- // ManualSample/V1/PurchaseOrderDto.cs
17
- public sealed record PurchaseOrderDto
18
- {
19
- public Guid Id { get; init; }
20
- public string CustomerName { get; init; } = null!;
21
- public decimal Amount { get; init; }
22
- public PurchaseOrderStatus Status { get; init; }
23
- public string CreatedBy { get; init; } = null!;
24
- }
25
- ```
26
-
27
- This carries **no attribute at all**, yet `mapper.Map<PurchaseOrderDto>(order)` and
28
- `mapper.ResultOf<PurchaseOrderDto>(order)` both work correctly. Mapster's global convention
29
- (`TypeAdapterConfig.GlobalSettings.Default`, configured once in `AppSetup.cs`) maps any two types by
30
- flexible name matching with no registration required — a hand-written DTO whose property names
31
- match the entity's needs nothing further.
13
+ **Default to `[GenerateDto]`.** It keeps the DTO in step with the entity (a new property appears on
14
+ the next build), carries `[SensitiveData]`/`[MaxLength]` across from the entity for free, and is what
15
+ the generated CRUD and generic list routes read as their contract. Narrow it with `Exclude`/`Include`
16
+ rather than abandoning it, and add a computed member on your own partial declaration rather than
17
+ hand-writing the whole record. Hand-write a DTO only when the response must intentionally expose
18
+ less than the entity *and* the feature has no generated route, or when a value needs CLR-only
19
+ computation that could never be EF-translated.
32
20
 
33
21
  **Generated** — one attribute on an empty `partial record`:
34
22
 
@@ -67,6 +55,26 @@ that's how `SupplierCostPrice`/`SupplierReferenceCode` stay gated per caller (se
67
55
  `GrossMargin` is the file's one hand-written addition, on the *other*, non-generated partial
68
56
  declaration — the generator's output file is never edited directly.
69
57
 
58
+ **Hand-written (fallback)** — a plain record, exactly the fields you list:
59
+
60
+ ```csharp
61
+ // ManualSample/V1/PurchaseOrderDto.cs
62
+ public sealed record PurchaseOrderDto
63
+ {
64
+ public Guid Id { get; init; }
65
+ public string CustomerName { get; init; } = null!;
66
+ public decimal Amount { get; init; }
67
+ public PurchaseOrderStatus Status { get; init; }
68
+ public string CreatedBy { get; init; } = null!;
69
+ }
70
+ ```
71
+
72
+ This carries **no attribute at all**, yet `mapper.Map<PurchaseOrderDto>(order)` and
73
+ `mapper.ResultOf<PurchaseOrderDto>(order)` both work correctly. Mapster's global convention
74
+ (`TypeAdapterConfig.GlobalSettings.Default`, configured once in `AppSetup.cs`) maps any two types by
75
+ flexible name matching with no registration required — a hand-written DTO whose property names
76
+ match the entity's needs nothing further.
77
+
70
78
  ## `[GenerateDto]` options
71
79
 
72
80
  ```csharp
@@ -96,7 +104,7 @@ real mapped columns covering the same intent.
96
104
 
97
105
  ## Mapster global configuration
98
106
 
99
- `Minimal.AppServices/AppSetup.cs`, run once at startup:
107
+ `<YourApp>.AppServices/AppSetup.cs`, run once at startup:
100
108
 
101
109
  ```csharp
102
110
  TypeAdapterConfig.GlobalSettings.Default.NameMatchingStrategy(NameMatchingStrategy.Flexible);
@@ -109,7 +117,7 @@ services.AddSingleton(TypeAdapterConfig.GlobalSettings)
109
117
  .AddScoped<IMapper, ServiceMapper>();
110
118
  ```
111
119
 
112
- `ScanMaps()` (`Minimal.AppServices/Extensions/MapsToExtensions.cs`) reflects over the assembly for
120
+ `ScanMaps()` (`<YourApp>.AppServices/Extensions/MapsToExtensions.cs`) reflects over the assembly for
113
121
  every type carrying `[MapsFrom(typeof(Entity))]` or `[GenerateDto(typeof(Entity))]` and calls
114
122
  `config.NewConfig(entityType, dtoType)` for each — this is what makes a generated/`[MapsFrom]`-typed
115
123
  pair eagerly compiled and validated at startup, rather than resolved lazily by the `Default`
@@ -118,7 +126,7 @@ fallback rule on first use. **Ordering matters**: only *after* that loop does it
118
126
  merges their `ForType` customizations onto the config the loop just built. Reversing the order would
119
127
  have the convention's `NewConfig` wipe out the `IRegister`'s merge.
120
128
 
121
- `[MapsFrom]` on a hand-written DTO (`Minimal.AppServices.Extensions.MapsFromAttribute`) is **not
129
+ `[MapsFrom]` on a hand-written DTO (`<YourApp>.AppServices.Extensions.MapsFromAttribute`) is **not
122
130
  required for the mapping to work** — `PurchaseOrderDto` proves that with zero attributes and zero
123
131
  registration. Reach for it only when you also want to attach a Mapster `IRegister` customization
124
132
  (a computed property, a rename) to that specific entity/DTO pair: `[MapsFrom]` puts the hand-written
@@ -215,7 +223,7 @@ carries its paging metadata (page number, total count) forward onto the DTO-type
215
223
 
216
224
  ## JSON contract
217
225
 
218
- `Minimal.Share/SharedConsts.JsonSerializerOptions` is the one source of truth for serialization
226
+ `<YourApp>.Share/SharedConsts.JsonSerializerOptions` is the one source of truth for serialization
219
227
  shape — camelCase property names, nulls omitted (`DefaultIgnoreCondition.WhenWritingNull`), enums as
220
228
  camelCase strings (`JsonStringEnumConverter(JsonNamingPolicy.CamelCase)`). `ServiceConfigs.AddOptions`
221
229
  copies these settings onto ASP.NET Core's own `JsonOptions` via `ConfigureHttpJsonOptions`, so a
@@ -236,10 +244,10 @@ change to the DTO type itself.
236
244
  | Derived/computed value | Any C# expression, `AfterMapping` included | Only via a hand-written partial member + `IRegister`, and only if EF-translatable |
237
245
  | Needs `[MapsFrom]`? | Only to attach an `IRegister` customization | N/A — `[GenerateDto]` already opts in |
238
246
 
239
- Pick hand-written when the response should intentionally expose less than the entity, or when a
240
- value needs CLR-only computation (`AfterMapping`, external lookups) and will never back a generated
241
- list route. Pick `[GenerateDto]` when the entity already carries `[CrudCreate]`/`[CrudUpdate]` and
242
- the DTO doubles as the generic CRUD/list contract.
247
+ Start at `[GenerateDto]` — always, when the entity carries `[CrudCreate]`/`[CrudUpdate]`, and by
248
+ default otherwise too. Drop to a hand-written record only when the response must intentionally expose
249
+ less than the entity *and* no generated route reads it, or when a value needs CLR-only computation
250
+ (`AfterMapping`, external lookups) that will never back a generated list route.
243
251
 
244
252
  ## Common mistakes
245
253
 
@@ -14,7 +14,7 @@ template — not `[RaisesEvent]`, not `[CrudCreate]`/`[CrudUpdate]`/`[CrudAction
14
14
 
15
15
  ## Mapper
16
16
 
17
- `Minimal.Infra/Features/ManualSample/Mappers/PurchaseOrderConfigs.cs` — the whole file:
17
+ `<YourApp>.Infra/Features/ManualSample/Mappers/PurchaseOrderConfigs.cs` — the whole file:
18
18
 
19
19
  ```csharp
20
20
  internal sealed class PurchaseOrderConfigs : DefaultEntityTypeConfiguration<PurchaseOrder>
@@ -32,7 +32,7 @@ internal sealed class PurchaseOrderConfigs : DefaultEntityTypeConfiguration<Purc
32
32
  }
33
33
  ```
34
34
 
35
- `Minimal.Infra/Features/AutomatedSample/Mappers/ProductConfigs.cs` — the whole file:
35
+ `<YourApp>.Infra/Features/AutomatedSample/Mappers/ProductConfigs.cs` — the whole file:
36
36
 
37
37
  ```csharp
38
38
  internal sealed class ProductConfigs : DefaultEntityTypeConfiguration<Product>
@@ -52,7 +52,7 @@ internal sealed class ProductConfigs : DefaultEntityTypeConfiguration<Product>
52
52
  }
53
53
  ```
54
54
 
55
- Location: `Minimal.Infra/Features/<Feature>/Mappers/<Entity>Configs.cs`. Discovered by
55
+ Location: `<YourApp>.Infra/Features/<Feature>/Mappers/<Entity>Configs.cs`. Discovered by
56
56
  `UseAutoConfigModel([typeof(CoreDbContext).Assembly, typeof(Sequences).Assembly])` — an **assembly
57
57
  scan for every `IEntityTypeConfiguration<T>`**, not a Scrutor convention scan (see Infra services,
58
58
  below, for what Scrutor actually is and isn't used for here). This call is wired in **both**
@@ -103,7 +103,7 @@ configured where its owner is, not in a shared file.
103
103
 
104
104
  ## Static data seeding
105
105
 
106
- `Minimal.Infra/Features/ManualSample/StaticData/PurchaseOrderStaticData.cs` — the whole file:
106
+ `<YourApp>.Infra/Features/ManualSample/StaticData/PurchaseOrderStaticData.cs` — the whole file:
107
107
 
108
108
  ```csharp
109
109
  internal sealed class PurchaseOrderStaticData : DataSeedingConfiguration<PurchaseOrder>
@@ -127,7 +127,7 @@ internal sealed class PurchaseOrderStaticData : DataSeedingConfiguration<Purchas
127
127
  }
128
128
  ```
129
129
 
130
- Location: `Minimal.Infra/Features/<Feature>/StaticData/<Entity>StaticData.cs`. Inherit the
130
+ Location: `<YourApp>.Infra/Features/<Feature>/StaticData/<Entity>StaticData.cs`. Inherit the
131
131
  **base class** `DataSeedingConfiguration<T>` (`DKNet.EfCore.Extensions.Configurations`) — not an
132
132
  `IDataSeedingConfiguration<T>` interface. Override the `protected` `GetDataAsync(CancellationToken)`
133
133
  and return the fixed rows via the entity's `internal` rehydration constructor (known `Guid`s,
@@ -149,9 +149,9 @@ first seeded.
149
149
  for every `IDataSeedingConfiguration` implementer by assembly scan (same style as
150
150
  `UseAutoConfigModel`, not Scrutor) and must be called in **both**:
151
151
 
152
- - `Minimal.Infra/Extensions/InfraSetup.cs` → `AddInfraServices` (the DI-registered `CoreDbContext`
152
+ - `<YourApp>.Infra/Extensions/InfraSetup.cs` → `AddInfraServices` (the DI-registered `CoreDbContext`
153
153
  the running app uses)
154
- - `Minimal.Infra/Extensions/InfraMigration.cs` → `MigrateDb` (a **separate** `CoreDbContext` built
154
+ - `<YourApp>.Infra/Extensions/InfraMigration.cs` → `MigrateDb` (a **separate** `CoreDbContext` built
155
155
  for the startup-migration path; seeding runs as part of `db.Database.MigrateAsync()`)
156
156
 
157
157
  This is a real bug the template hit once already: `PurchaseOrderStaticData` was correctly discovered
@@ -159,22 +159,22 @@ by the DI-path context but the migration path built its own context without the
159
159
  `.UseAutoDataSeeding(...)` call, so seed rows never appeared in a real database even though the
160
160
  migration itself ran. When adding new seed data, verify both call sites, not just one.
161
161
 
162
- **No test fixture wires this.** Neither `Minimal.App.Tests`' `ApiFixture` nor
163
- `Minimal.App.BDDTests`' `BddApiFactory` calls `.UseAutoDataSeeding(...)` on their in-memory
162
+ **No test fixture wires this.** Neither `<YourApp>.App.Tests`' `ApiFixture` nor
163
+ `<YourApp>.App.BDDTests`' `BddApiFactory` calls `.UseAutoDataSeeding(...)` on their in-memory
164
164
  `DbContext` — that wiring exists only in the two real composition-root call sites above. Tests that
165
165
  actually exercise seeded data:
166
166
 
167
- - `Minimal.App.Tests/Unit/ManualSample/PurchaseOrderStaticDataTests.cs` — invokes the `protected
167
+ - `<YourApp>.App.Tests/Unit/ManualSample/PurchaseOrderStaticDataTests.cs` — invokes the `protected
168
168
  GetDataAsync` via reflection (nothing public exposes it for a direct call) and asserts the three
169
169
  fixed rows, owned by `SharedConsts.SystemAccount`, with distinct `Id`s.
170
- - `Minimal.App.Tests/Integration/ManualSample/V1/InfraMigrationSeedingTests.cs` — runs
170
+ - `<YourApp>.App.Tests/Integration/ManualSample/V1/InfraMigrationSeedingTests.cs` — runs
171
171
  `InfraMigration.MigrateDb` itself against a real, ephemeral Postgres `Testcontainers` instance,
172
172
  then reads `/v1/purchase-orders` over HTTP. This is the one test that fails if
173
173
  `.UseAutoDataSeeding(...)` is ever removed from `InfraMigration.MigrateDb`.
174
174
 
175
175
  ## `CoreDbContext`
176
176
 
177
- `Minimal.Infra/Contexts/CoreDbContext.cs` — `internal class CoreDbContext(DbContextOptions options,
177
+ `<YourApp>.Infra/Contexts/CoreDbContext.cs` — `internal class CoreDbContext(DbContextOptions options,
178
178
  IEnumerable<IDataOwnerProvider>? dataKeyProviders = null) : DbContext(options), IDataOwnerDbContext`.
179
179
  No `DbSet<T>` declarations anywhere — the model is built entirely from the `IEntityTypeConfiguration<T>`
180
180
  scan. It exposes `AccessibleKeys` from the first registered `IDataOwnerProvider`
@@ -235,7 +235,7 @@ internal static DbContextOptionsBuilder UseNpgsqlWithMigration(
235
235
  .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
236
236
  ```
237
237
 
238
- `Minimal.Infra/Extensions/InfraMigration.cs` — the startup-migration path, in full:
238
+ `<YourApp>.Infra/Extensions/InfraMigration.cs` — the startup-migration path, in full:
239
239
 
240
240
  ```csharp
241
241
  public static async Task MigrateDb(string connectionString)
@@ -253,7 +253,7 @@ public static async Task MigrateDb(string connectionString)
253
253
  ```
254
254
 
255
255
  `AddSpecRepo<CoreDbContext>()` wires `IRepositorySpec` (see `dknet-queries-specs`).
256
- `AddEventPublisher<CoreDbContext, EventPublisher>()` wires `Minimal.Infra/Services/EventPublisher.cs`
256
+ `AddEventPublisher<CoreDbContext, EventPublisher>()` wires `<YourApp>.Infra/Services/EventPublisher.cs`
257
257
  — a `DefaultEventPublisher` override that does `bus.Publish(eventObj)` — as the sink both raise
258
258
  styles (`AddEvent`/`[RaisesEvent]`) publish through after a successful save.
259
259
 
@@ -263,28 +263,28 @@ Always from the solution's `ApiEndpoints/` directory (there is no wrapper script
263
263
  `dotnet ef` command):
264
264
 
265
265
  ```bash
266
- dotnet ef migrations add <Name> -c CoreDbContext -p Minimal.Infra/Minimal.Infra.csproj
267
- dotnet ef migrations remove -c CoreDbContext -p Minimal.Infra/Minimal.Infra.csproj
266
+ dotnet ef migrations add <Name> -c CoreDbContext -p <YourApp>.Infra/<YourApp>.Infra.csproj
267
+ dotnet ef migrations remove -c CoreDbContext -p <YourApp>.Infra/<YourApp>.Infra.csproj
268
268
  ```
269
269
 
270
- Inspect the generated migration under `Minimal.Infra/Migrations/` before continuing — confirm the
270
+ Inspect the generated migration under `<YourApp>.Infra/Migrations/` before continuing — confirm the
271
271
  table, columns, and any index/constraint match what the mapper declares. Never edit an
272
272
  already-applied migration; add a new one instead. `Architecture/MigrationSchemaTests.cs` pins
273
273
  specific facts about the compiled model: `Product.Name` has a **unique** single-column index,
274
274
  the model declares a sequence in schema `seq` and a sequence named `Seq_Membership` (never one named
275
- after `Sequences.None`), the provider is not SQL Server, and both `Minimal.AppHost` and
276
- `Minimal.Infra` reference PostgreSQL/Npgsql packages, never SQL Server ones. At application start,
275
+ after `Sequences.None`), the provider is not SQL Server, and both `<YourApp>.AppHost` and
276
+ `<YourApp>.Infra` reference PostgreSQL/Npgsql packages, never SQL Server ones. At application start,
277
277
  `FeatureManagement:RunDbMigrationWhenAppStart` (or the `migration` launch argument — `dotnet run
278
- --project ApiEndpoints/Minimal.Api -- migration`) is what actually calls this path; removing a
278
+ --project ApiEndpoints/<YourApp>.Api -- migration`) is what actually calls this path; removing a
279
279
  feature's tables is a **drop migration**, covered by `dknet-feature-lifecycle`, not here.
280
280
 
281
281
  ## Infra domain services
282
282
 
283
- `Minimal.Domains/Services/` holds the interface, `Minimal.Infra/Services/` the implementation,
283
+ `<YourApp>.Domains/Services/` holds the interface, `<YourApp>.Infra/Services/` the implementation,
284
284
  `InfraSetup.AddInfraServices` the registration — **by explicit `AddScoped<TInterface,
285
285
  TImplementation>()`, one line per service.** There is no Scrutor convention scan anywhere in this
286
- template's `Minimal.Infra` registration path — the only `Scan(` call in the whole solution is
287
- Mapster's `TypeAdapterConfig.Scan` in `Minimal.AppServices/Extensions/MapsToExtensions.cs`, unrelated
286
+ template's `<YourApp>.Infra` registration path — the only `Scan(` call in the whole solution is
287
+ Mapster's `TypeAdapterConfig.Scan` in `<YourApp>.AppServices/Extensions/MapsToExtensions.cs`, unrelated
288
288
  to service registration. Do not rely on a naming convention or namespace
289
289
  (`.Services`/`.Repos`) to get a service registered — add the `AddScoped` line yourself. `internal
290
290
  sealed` is still required by the same architecture rule that covers mappers/handlers/validators
@@ -294,7 +294,7 @@ implementation like `MembershipService` isn't targeted by that rule either, but
294
294
  `internal sealed` convention regardless).
295
295
 
296
296
  ```csharp
297
- // Minimal.Infra/Services/SequenceService.cs — internal abstract base, one per sequence-backed service
297
+ // <YourApp>.Infra/Services/SequenceService.cs — internal abstract base, one per sequence-backed service
298
298
  internal abstract class SequenceService(DbContext dbContext, Sequences sequence) : ISequenceServices
299
299
  {
300
300
  public virtual async ValueTask<string> NextValueAsync() =>
@@ -303,7 +303,7 @@ internal abstract class SequenceService(DbContext dbContext, Sequences sequence)
303
303
  : Guid.NewGuid().ToString();
304
304
  }
305
305
 
306
- // Minimal.Infra/Services/MembershipService.cs — the whole file
306
+ // <YourApp>.Infra/Services/MembershipService.cs — the whole file
307
307
  internal sealed class MembershipService(CoreDbContext dbContext)
308
308
  : SequenceService(dbContext, Sequences.Membership), IMembershipService;
309
309
  ```
@@ -311,19 +311,19 @@ internal sealed class MembershipService(CoreDbContext dbContext)
311
311
  `NextValueAsync()` formats the next value with the sequence's `FormatString` on PostgreSQL
312
312
  (`NextSeqValueWithFormat`), and falls back to a plain `Guid` when the context isn't Npgsql — so a
313
313
  unit test against an in-memory/SQLite context never needs a real Postgres sequence. Add your own
314
- service the same way: an interface in `Minimal.Domains/Services/`, an `internal sealed`
315
- implementation in `Minimal.Infra/Services/`, one `AddScoped` line in `AddInfraServices`.
314
+ service the same way: an interface in `<YourApp>.Domains/Services/`, an `internal sealed`
315
+ implementation in `<YourApp>.Infra/Services/`, one `AddScoped` line in `AddInfraServices`.
316
316
 
317
- ## Architecture tests constraining `Minimal.Infra`
317
+ ## Architecture tests constraining `<YourApp>.Infra`
318
318
 
319
319
  `Architecture/InfraTests.cs`:
320
320
 
321
321
  - `AllEfConfigClassesShouldBeInternalAndSealed` — every `IEntityTypeConfiguration<T>` implementer.
322
322
  - `AllSeedingDataClassesShouldBeInternalAndSealed` — every `IDataSeedingConfiguration` implementer.
323
323
  - `AllHandlerClassesShouldBeInternalAndSealed` — every `IRequestHandler<>`/`IRequestHandler<,>`/
324
- `IConsumer<>` implementer (covers `Minimal.Infra/Features/*/ExternalEvents/*` consumers).
324
+ `IConsumer<>` implementer (covers `<YourApp>.Infra/Features/*/ExternalEvents/*` consumers).
325
325
  - `AllValidatorClassesShouldBeInternalAndSealed` — every `AbstractValidator<T>` subclass found in
326
- `Minimal.Infra` (none shipped there today; validators live in `Minimal.AppServices`).
326
+ `<YourApp>.Infra` (none shipped there today; validators live in `<YourApp>.AppServices`).
327
327
  - `AllEnumProperties_StoringToDb_ShouldHaveStringConversion` — every enum property in the built
328
328
  model must have `ProviderClrType == typeof(string)`.
329
329
  - `NoEntityString_ShouldBe_ConfiguredAs_Max` — every mapped `string` property must have an explicit
@@ -334,34 +334,34 @@ implementation in `Minimal.Infra/Services/`, one `AddScoped` line in `AddInfraSe
334
334
 
335
335
  ## Step-by-step
336
336
 
337
- 1. Create `Minimal.Infra/Features/<Feature>/Mappers/<Entity>Configs.cs`: `internal sealed class
337
+ 1. Create `<YourApp>.Infra/Features/<Feature>/Mappers/<Entity>Configs.cs`: `internal sealed class
338
338
  <Entity>Configs : DefaultEntityTypeConfiguration<Entity>`, call `base.Configure(builder)` first,
339
339
  then indexes, `HasMaxLength`/`HasPrecision` on every column, `HasConversion<string>()` on every
340
340
  enum, `ToTable("<Plural>", "<schema>")`.
341
- 2. If the feature needs reference data, create `Minimal.Infra/Features/<Feature>/StaticData/<Entity>StaticData.cs`:
341
+ 2. If the feature needs reference data, create `<YourApp>.Infra/Features/<Feature>/StaticData/<Entity>StaticData.cs`:
342
342
  `internal sealed class <Entity>StaticData : DataSeedingConfiguration<Entity>`, override
343
343
  `GetDataAsync`, build rows via the entity's `internal` rehydration constructor with fixed `Guid`s
344
344
  and `SharedConsts.SystemAccount`.
345
- 3. If the feature needs a new domain service, add the interface under `Minimal.Domains/Services/`,
346
- an `internal sealed` implementation under `Minimal.Infra/Services/`, and one `AddScoped<...>()`
345
+ 3. If the feature needs a new domain service, add the interface under `<YourApp>.Domains/Services/`,
346
+ an `internal sealed` implementation under `<YourApp>.Infra/Services/`, and one `AddScoped<...>()`
347
347
  line in `InfraSetup.AddInfraServices`.
348
348
  4. Generate and inspect the migration from `ApiEndpoints/`:
349
- `dotnet ef migrations add <Name> -c CoreDbContext -p Minimal.Infra/Minimal.Infra.csproj`.
349
+ `dotnet ef migrations add <Name> -c CoreDbContext -p <YourApp>.Infra/<YourApp>.Infra.csproj`.
350
350
  5. `dotnet build -c Release` and `dotnet test --settings coverage.runsettings` from the solution
351
351
  root.
352
352
 
353
353
  ## Validation checklist
354
354
 
355
355
  - [ ] Mapper inherits `DefaultEntityTypeConfiguration<TEntity>` and calls `base.Configure(builder)` first
356
- - [ ] Mapper class is `internal sealed`, under `Minimal.Infra/Features/<Feature>/Mappers/`
356
+ - [ ] Mapper class is `internal sealed`, under `<YourApp>.Infra/Features/<Feature>/Mappers/`
357
357
  - [ ] Every mapped `string` has an explicit `HasMaxLength`; every mapped enum has `HasConversion<string>()`
358
358
  - [ ] A real business uniqueness needs `.IsUnique()` on the index, not just a validator check
359
359
  - [ ] `ToTable("Name", "schema")` set — literal string or a `DomainSchemas` constant
360
360
  - [ ] If seeding: class is `internal sealed`, extends `DataSeedingConfiguration<T>` (not an
361
361
  interface), uses the entity's rehydration constructor, and `UseAutoDataSeeding(...)` is
362
362
  present in **both** `InfraSetup.AddInfraServices` and `InfraMigration.MigrateDb`
363
- - [ ] If adding a domain service: interface in `Minimal.Domains/Services/`, `internal sealed`
364
- implementation in `Minimal.Infra/Services/`, explicit `AddScoped<...>()` line added — no
363
+ - [ ] If adding a domain service: interface in `<YourApp>.Domains/Services/`, `internal sealed`
364
+ implementation in `<YourApp>.Infra/Services/`, explicit `AddScoped<...>()` line added — no
365
365
  convention scan will pick it up on its own
366
366
  - [ ] Migration generated from `ApiEndpoints/` and inspected before continuing
367
367
  - [ ] `dotnet build -c Release` passes