@atlashub/smartstack-cli 1.35.0 → 1.37.0

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 (27) hide show
  1. package/package.json +1 -1
  2. package/templates/skills/_shared.md +7 -7
  3. package/templates/skills/application/steps/step-01-navigation.md +226 -43
  4. package/templates/skills/application/steps/step-03-roles.md +160 -38
  5. package/templates/skills/application/steps/step-04-backend.md +126 -19
  6. package/templates/skills/application/steps/step-05-frontend.md +4 -1
  7. package/templates/skills/application/templates-backend.md +8 -8
  8. package/templates/skills/application/templates-frontend.md +8 -8
  9. package/templates/skills/application/templates-seed.md +200 -1
  10. package/templates/skills/gitflow/_shared.md +188 -53
  11. package/templates/skills/gitflow/phases/abort.md +28 -16
  12. package/templates/skills/gitflow/phases/cleanup.md +13 -9
  13. package/templates/skills/gitflow/phases/status.md +16 -17
  14. package/templates/skills/gitflow/steps/step-commit.md +11 -5
  15. package/templates/skills/gitflow/steps/step-finish.md +43 -33
  16. package/templates/skills/gitflow/steps/step-init.md +7 -2
  17. package/templates/skills/gitflow/steps/step-merge.md +24 -10
  18. package/templates/skills/gitflow/steps/step-pr.md +42 -28
  19. package/templates/skills/gitflow/steps/step-start.md +19 -13
  20. package/templates/skills/gitflow/templates/config.json +7 -4
  21. package/templates/skills/ralph-loop/SKILL.md +57 -11
  22. package/templates/skills/ralph-loop/steps/step-00-init.md +170 -30
  23. package/templates/skills/ralph-loop/steps/step-01-task.md +243 -40
  24. package/templates/skills/ralph-loop/steps/step-02-execute.md +142 -24
  25. package/templates/skills/ralph-loop/steps/step-03-commit.md +140 -36
  26. package/templates/skills/ralph-loop/steps/step-04-check.md +128 -44
  27. package/templates/skills/ralph-loop/steps/step-05-report.md +175 -88
@@ -80,15 +80,15 @@ Args:
80
80
 
81
81
  ### 4. Parse MCP Response
82
82
 
83
- The tool generates:
84
- - `Domain/{EntityName}.cs` - Entity with BaseEntity
85
- - `Infrastructure/Persistence/Configurations/{EntityName}Configuration.cs` - EF Config
86
- - `Application/Services/I{EntityName}Service.cs` - Interface
87
- - `Application/Services/{EntityName}Service.cs` - Implementation
88
- - `Api/Controllers/{EntityName}Controller.cs` - REST Controller
89
- - `Application/DTOs/{EntityName}Dto.cs` - Response DTO
90
- - `Application/DTOs/Create{EntityName}Dto.cs` - Create request
91
- - `Application/DTOs/Update{EntityName}Dto.cs` - Update request
83
+ The tool generates (paths organized by navRoute hierarchy `{context}.{application}.{module}`):
84
+ - `Domain/{Context}/{Application}/{Module}/{EntityName}.cs` - Entity with BaseEntity
85
+ - `Infrastructure/Persistence/Configurations/{Module}/{EntityName}Configuration.cs` - EF Config
86
+ - `Application/Common/Interfaces/I{EntityName}Service.cs` - Service Interface
87
+ - `Infrastructure/Services/{Module}/{EntityName}Service.cs` - Service Implementation
88
+ - `Api/Controllers/{Context}/{EntityName}Controller.cs` - REST Controller
89
+ - `Application/{Context}/{Application}/{Module}/DTOs/{EntityName}ResponseDto.cs` - Response DTO
90
+ - `Application/{Context}/{Application}/{Module}/DTOs/Create{EntityName}Dto.cs` - Create request
91
+ - `Application/{Context}/{Application}/{Module}/DTOs/Update{EntityName}Dto.cs` - Update request
92
92
 
93
93
  ### 5. Present Output to User
94
94
 
@@ -96,20 +96,20 @@ The tool generates:
96
96
  ## Backend Code Generated
97
97
 
98
98
  ### Domain Layer
99
- - `Domain/{EntityName}.cs`
99
+ - `Domain/{Context}/{Application}/{Module}/{EntityName}.cs`
100
100
 
101
101
  ### Infrastructure Layer
102
- - `Persistence/Configurations/{EntityName}Configuration.cs`
102
+ - `Persistence/Configurations/{Module}/{EntityName}Configuration.cs`
103
+ - `Services/{Module}/{EntityName}Service.cs`
103
104
 
104
105
  ### Application Layer
105
- - `Services/I{EntityName}Service.cs`
106
- - `Services/{EntityName}Service.cs`
107
- - `DTOs/{EntityName}Dto.cs`
108
- - `DTOs/Create{EntityName}Dto.cs`
109
- - `DTOs/Update{EntityName}Dto.cs`
106
+ - `Common/Interfaces/I{EntityName}Service.cs`
107
+ - `{Context}/{Application}/{Module}/DTOs/{EntityName}ResponseDto.cs`
108
+ - `{Context}/{Application}/{Module}/DTOs/Create{EntityName}Dto.cs`
109
+ - `{Context}/{Application}/{Module}/DTOs/Update{EntityName}Dto.cs`
110
110
 
111
111
  ### API Layer
112
- - `Controllers/{EntityName}Controller.cs`
112
+ - `Controllers/{Context}/{EntityName}Controller.cs`
113
113
  - NavRoute: `{full_path}`
114
114
  - Endpoints:
115
115
  - GET /api/{code} - List all
@@ -124,7 +124,110 @@ The tool generates:
124
124
  3. Run: `dotnet ef migrations add core_vX.X.X_XXX_Add{EntityName}`
125
125
  ```
126
126
 
127
- ### 6. Store Entity Info
127
+ ### 6. Entity Seeding (Optional)
128
+
129
+ > Ask the user if they want to seed initial data for the entity.
130
+ > This creates runtime seed data (applied at startup via DevDataSeeder), following the same
131
+ > architecture as core (SeedData provider pattern).
132
+
133
+ ```yaml
134
+ questions:
135
+ - header: "Seed Data"
136
+ question: "Would you like to generate initial seed data for {EntityName}?"
137
+ options:
138
+ - label: "Yes - Generate SeedData provider"
139
+ description: "Creates {EntityName}SeedData.cs + registers in DevDataSeeder (same pattern as core)"
140
+ - label: "No - Skip seeding"
141
+ description: "Entity starts empty (can add seed data later)"
142
+ multiSelect: false
143
+ ```
144
+
145
+ #### If User Selects YES:
146
+
147
+ **Reference:** See `templates-seed.md` section "TEMPLATE: ENTITY SEED DATA (SeedData Provider)"
148
+
149
+ **Generate the following files:**
150
+
151
+ 1. **`Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs`**
152
+
153
+ Follow the SmartStack.app pattern (same architecture as core):
154
+ - Static class with `internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()`
155
+ - Use deterministic GUIDs (NEVER `Guid.NewGuid()`)
156
+ - Include 3-5 sample entities with varied, realistic data
157
+ - Internal record class `{EntityName}SeedItem` with all entity properties
158
+ - For multi-tenant entities: organize by tenant using `GetTenant1{EntityName}s()` helper methods
159
+ - Reference existing tenant/user IDs from `TenantSeedData`/`UserSeedData` for foreign keys
160
+
161
+ ```csharp
162
+ // Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs
163
+
164
+ public static class {EntityName}SeedData
165
+ {
166
+ public static readonly Guid Sample1Id = Guid.Parse("...");
167
+ public static readonly Guid Sample2Id = Guid.Parse("...");
168
+ public static readonly Guid Sample3Id = Guid.Parse("...");
169
+
170
+ internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
171
+ {
172
+ return new[]
173
+ {
174
+ new {EntityName}SeedItem { Id = Sample1Id, /* ... */ },
175
+ new {EntityName}SeedItem { Id = Sample2Id, /* ... */ },
176
+ new {EntityName}SeedItem { Id = Sample3Id, /* ... */ }
177
+ };
178
+ }
179
+ }
180
+
181
+ internal class {EntityName}SeedItem
182
+ {
183
+ public Guid Id { get; init; }
184
+ // ... all required entity properties
185
+ }
186
+ ```
187
+
188
+ 2. **Update `Infrastructure/Persistence/Seeding/DevDataSeeder.cs`**
189
+ - Add `using` for the new SeedData namespace
190
+ - Add `await Seed{EntityName}sAsync(cancellationToken);` in `SeedAsync()` method
191
+ - Add private seeding method:
192
+
193
+ ```csharp
194
+ private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
195
+ {
196
+ _logger.LogInformation("Seeding demo {EntityName}s...");
197
+
198
+ var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
199
+ if (existingCount > 0)
200
+ {
201
+ _logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
202
+ return;
203
+ }
204
+
205
+ var createdCount = 0;
206
+ foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
207
+ {
208
+ var entity = {EntityName}.Create(/* map seedItem properties */);
209
+ typeof({EntityName}).GetProperty("Id")?.SetValue(entity, seedItem.Id);
210
+ _context.{EntityName}s.Add(entity);
211
+ createdCount++;
212
+ }
213
+
214
+ if (createdCount > 0)
215
+ await _context.SaveChangesAsync(cancellationToken);
216
+
217
+ _logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
218
+ }
219
+ ```
220
+
221
+ #### If User Selects NO:
222
+
223
+ Skip this section and proceed to storing entity info.
224
+
225
+ ```markdown
226
+ > Skipping entity seed data. You can generate it later using the SeedData provider pattern
227
+ > documented in templates-seed.md.
228
+ ```
229
+
230
+ ### 7. Store Entity Info
128
231
 
129
232
  Store entity information for frontend generation:
130
233
 
@@ -132,6 +235,7 @@ Store entity information for frontend generation:
132
235
  {entity_name} = "{EntityName}"
133
236
  {entity_code} = "{code}"
134
237
  {api_route} = "/api/{code}"
238
+ {has_seed_data} = true/false // Whether SeedData provider was generated
135
239
  ```
136
240
 
137
241
  ---
@@ -168,6 +272,7 @@ This ensures:
168
272
  If MCP returns successfully:
169
273
  - Display all generated files
170
274
  - Show DbSet and DI registration instructions
275
+ - Ask about entity seeding (section 6)
171
276
  - Store entity info for frontend
172
277
  - Proceed to step-05-frontend.md
173
278
 
@@ -186,6 +291,7 @@ If MCP call fails:
186
291
  - Entity, Service, Controller generated
187
292
  - DTOs generated
188
293
  - NavRoute attribute included
294
+ - Entity seeding offered to user (SeedData.cs + DevDataSeeder if accepted)
189
295
  - Entity info stored for frontend
190
296
  - Proceeded to step-05-frontend.md
191
297
 
@@ -199,4 +305,5 @@ If MCP call fails:
199
305
 
200
306
  ## NEXT STEP
201
307
 
202
- After displaying backend code, proceed to `./step-05-frontend.md`
308
+ After backend code is generated and entity seeding is handled,
309
+ proceed to `./step-05-frontend.md`
@@ -129,9 +129,12 @@ Repeat for en, it, de with appropriate translations.
129
129
  ```markdown
130
130
  ## Frontend Code Generated
131
131
 
132
- ### Components
132
+ ### Pages
133
133
  - `pages/{context}/{application}/{module}/{EntityName}Page.tsx`
134
134
  - `pages/{context}/{application}/{module}/{EntityName}ListView.tsx`
135
+
136
+ ### Components
137
+ - `components/{context}/{module}/{EntityName}.tsx`
135
138
  - `hooks/use{EntityName}.ts`
136
139
 
137
140
  ### API Client
@@ -49,7 +49,7 @@ public static class Erp
49
49
  ```csharp
50
50
  // src/SmartStack.Application/Common/Interfaces/I{Module}Service.cs
51
51
 
52
- using SmartStack.Application.$MODULE_PASCAL.Models;
52
+ using SmartStack.Application.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;
53
53
 
54
54
  namespace SmartStack.Application.Common.Interfaces;
55
55
 
@@ -88,8 +88,8 @@ public interface I$MODULE_PASCALService
88
88
  using Microsoft.EntityFrameworkCore;
89
89
  using Microsoft.Extensions.Logging;
90
90
  using SmartStack.Application.Common.Interfaces;
91
- using SmartStack.Application.$MODULE_PASCAL.Models;
92
- using SmartStack.Domain.$MODULE_PASCAL;
91
+ using SmartStack.Application.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;
92
+ using SmartStack.Domain.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
93
93
 
94
94
  namespace SmartStack.Infrastructure.Services.$MODULE_PASCAL;
95
95
 
@@ -269,9 +269,9 @@ public class $MODULE_PASCALService : I$MODULE_PASCALService
269
269
  ## TEMPLATE: DTOs
270
270
 
271
271
  ```csharp
272
- // src/SmartStack.Application/$MODULE_PASCAL/Models/$ENTITY_PASCALDto.cs
272
+ // src/SmartStack.Application/$CONTEXT_PASCAL/$APPLICATION_PASCAL/$MODULE_PASCAL/DTOs/$ENTITY_PASCALDto.cs
273
273
 
274
- namespace SmartStack.Application.$MODULE_PASCAL.Models;
274
+ namespace SmartStack.Application.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL.DTOs;
275
275
 
276
276
  public record $ENTITY_PASCALDto
277
277
  {
@@ -310,11 +310,11 @@ public record $ENTITY_PASCALQueryParameters
310
310
  ## TEMPLATE: DOMAIN ENTITY
311
311
 
312
312
  ```csharp
313
- // src/SmartStack.Domain/$MODULE_PASCAL/$ENTITY_PASCAL.cs
313
+ // src/SmartStack.Domain/$CONTEXT_PASCAL/$APPLICATION_PASCAL/$MODULE_PASCAL/$ENTITY_PASCAL.cs
314
314
 
315
315
  using SmartStack.Domain.Common;
316
316
 
317
- namespace SmartStack.Domain.$MODULE_PASCAL;
317
+ namespace SmartStack.Domain.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
318
318
 
319
319
  public class $ENTITY_PASCAL : BaseEntity
320
320
  {
@@ -363,7 +363,7 @@ public class $ENTITY_PASCAL : BaseEntity
363
363
 
364
364
  using Microsoft.EntityFrameworkCore;
365
365
  using Microsoft.EntityFrameworkCore.Metadata.Builders;
366
- using SmartStack.Domain.$MODULE_PASCAL;
366
+ using SmartStack.Domain.$CONTEXT_PASCAL.$APPLICATION_PASCAL.$MODULE_PASCAL;
367
367
 
368
368
  namespace SmartStack.Infrastructure.Persistence.Configurations.$MODULE_PASCAL;
369
369
 
@@ -8,11 +8,11 @@
8
8
 
9
9
  ```
10
10
  web/smartstack-web/src/
11
- ├── pages/$CONTEXT/$MODULE/
11
+ ├── pages/$CONTEXT/$APPLICATION/$MODULE/
12
12
  │ ├── $MODULE_PASCALPage.tsx # Main page (list)
13
13
  │ ├── $MODULE_PASCALDetailPage.tsx # Detail page
14
14
  │ └── Create$MODULE_PASCALPage.tsx # Create page
15
- ├── components/$MODULE/
15
+ ├── components/$CONTEXT/$MODULE/
16
16
  │ ├── $MODULE_PASCALListView.tsx # Reusable list component
17
17
  │ ├── $MODULE_PASCALForm.tsx # CRUD form
18
18
  │ └── $MODULE_PASCALFilters.tsx # Filters
@@ -35,10 +35,10 @@ web/smartstack-web/src/
35
35
  ## TEMPLATE: MAIN PAGE
36
36
 
37
37
  ```tsx
38
- // pages/$CONTEXT/$MODULE/$MODULE_PASCALPage.tsx
38
+ // pages/$CONTEXT/$APPLICATION/$MODULE/$MODULE_PASCALPage.tsx
39
39
 
40
40
  import { useTranslation } from 'react-i18next';
41
- import { $MODULE_PASCALListView } from '@/components/$MODULE/$MODULE_PASCALListView';
41
+ import { $MODULE_PASCALListView } from '@/components/$CONTEXT/$MODULE/$MODULE_PASCALListView';
42
42
 
43
43
  export function $MODULE_PASCALPage() {
44
44
  const { t } = useTranslation(['$module', 'common']);
@@ -59,7 +59,7 @@ export function $MODULE_PASCALPage() {
59
59
  ## TEMPLATE: LIST VIEW (Reusable component)
60
60
 
61
61
  ```tsx
62
- // components/$MODULE/$MODULE_PASCALListView.tsx
62
+ // components/$CONTEXT/$MODULE/$MODULE_PASCALListView.tsx
63
63
 
64
64
  import { useState, useEffect } from 'react';
65
65
  import { useNavigate } from 'react-router-dom';
@@ -491,9 +491,9 @@ React Router v7 **requires** nested routes for multi-module applications.
491
491
  ```tsx
492
492
  // Add to App.tsx
493
493
 
494
- import { $MODULE_PASCALPage } from '@/pages/$CONTEXT/$MODULE/$MODULE_PASCALPage';
495
- import { $MODULE_PASCALDetailPage } from '@/pages/$CONTEXT/$MODULE/$MODULE_PASCALDetailPage';
496
- import { Create$MODULE_PASCALPage } from '@/pages/$CONTEXT/$MODULE/Create$MODULE_PASCALPage';
494
+ import { $MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/$MODULE_PASCALPage';
495
+ import { $MODULE_PASCALDetailPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage';
496
+ import { Create$MODULE_PASCALPage } from '@/pages/$CONTEXT/$APPLICATION/$MODULE/Create$MODULE_PASCALPage';
497
497
 
498
498
  // In routes - NESTED STRUCTURE
499
499
  <Route path="$APPLICATION">
@@ -605,12 +605,209 @@ $ORDER = 1
605
605
 
606
606
  ---
607
607
 
608
+ ## TEMPLATE: ENTITY SEED DATA (SeedData Provider)
609
+
610
+ > **Usage:** Runtime seed data for domain entities (Products, Orders, Tickets, etc.)
611
+ > **When:** User wants initial data for development/testing
612
+ > **Mechanism:** Loaded at startup by DevDataSeeder, NOT via EF Core HasData/migrations
613
+ > **Architecture:** Identical in core AND extensions
614
+
615
+ ### Two Seeding Mechanisms
616
+
617
+ | Mechanism | When to Use | Applied Via |
618
+ |-----------|-------------|-------------|
619
+ | **HasData()** in Configuration.cs | System entities (Navigation, Permissions, Roles) | EF Core migrations |
620
+ | **SeedData.cs + DevDataSeeder** | Domain entities (Tickets, Products, Orders) | Application startup |
621
+
622
+ **Rule:** Navigation, Permission, and RolePermission seeds use HasData().
623
+ Domain entity seeds use the SeedData provider pattern below.
624
+
625
+ ### SeedData Class Pattern
626
+
627
+ ```csharp
628
+ // Infrastructure/Persistence/Seeding/Data/{Domain}/{EntityName}SeedData.cs
629
+
630
+ using SmartStack.Domain.{Context}.{Application}.{Module};
631
+
632
+ namespace SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
633
+
634
+ /// <summary>
635
+ /// Demo {EntityName} data for development and testing.
636
+ /// </summary>
637
+ public static class {EntityName}SeedData
638
+ {
639
+ // ============================================================
640
+ // DETERMINISTIC IDs - for referencing in other seed data
641
+ // ============================================================
642
+
643
+ public static readonly Guid Sample1Id = Guid.Parse("$SEED_GUID_1");
644
+ public static readonly Guid Sample2Id = Guid.Parse("$SEED_GUID_2");
645
+ public static readonly Guid Sample3Id = Guid.Parse("$SEED_GUID_3");
646
+
647
+ /// <summary>
648
+ /// Returns all demo {EntityName} entities.
649
+ /// </summary>
650
+ internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
651
+ {
652
+ return new[]
653
+ {
654
+ new {EntityName}SeedItem
655
+ {
656
+ Id = Sample1Id,
657
+ // ... entity properties with realistic sample data
658
+ CreatedByUserId = UserSeedData.SystemUserId
659
+ },
660
+ new {EntityName}SeedItem
661
+ {
662
+ Id = Sample2Id,
663
+ // ... varied data
664
+ CreatedByUserId = UserSeedData.SystemUserId
665
+ },
666
+ new {EntityName}SeedItem
667
+ {
668
+ Id = Sample3Id,
669
+ // ... varied data
670
+ CreatedByUserId = UserSeedData.SystemUserId
671
+ }
672
+ };
673
+ }
674
+ }
675
+
676
+ /// <summary>
677
+ /// Seed item DTO for {EntityName} (avoids domain factory methods during seeding).
678
+ /// </summary>
679
+ internal class {EntityName}SeedItem
680
+ {
681
+ public Guid Id { get; init; }
682
+ // ... all required properties matching the domain entity
683
+ public Guid CreatedByUserId { get; init; }
684
+ }
685
+ ```
686
+
687
+ ### Multi-Tenant Entity Seed Pattern
688
+
689
+ For entities belonging to a tenant, organize by tenant:
690
+
691
+ ```csharp
692
+ internal static IEnumerable<{EntityName}SeedItem> GetAll{EntityName}s()
693
+ {
694
+ var items = new List<{EntityName}SeedItem>();
695
+
696
+ items.AddRange(GetTenant1{EntityName}s());
697
+ items.AddRange(GetTenant2{EntityName}s());
698
+
699
+ return items;
700
+ }
701
+
702
+ private static IEnumerable<{EntityName}SeedItem> GetTenant1{EntityName}s()
703
+ {
704
+ var tenantId = TenantSeedData.AcmeCorporationId;
705
+ var userId = TenantMembershipSeedData.AcmeOwnerId;
706
+
707
+ return new[]
708
+ {
709
+ new {EntityName}SeedItem
710
+ {
711
+ Id = Guid.Parse("$GUID"),
712
+ TenantId = tenantId,
713
+ CreatedByUserId = userId,
714
+ // ... properties
715
+ }
716
+ };
717
+ }
718
+ ```
719
+
720
+ ### DevDataSeeder Registration Pattern
721
+
722
+ ```csharp
723
+ // In Infrastructure/Persistence/Seeding/DevDataSeeder.cs
724
+
725
+ // 1. Add using statement
726
+ using SmartStack.Infrastructure.Persistence.Seeding.Data.{Domain};
727
+
728
+ // 2. Add call in SeedAsync() method (after existing seed calls)
729
+ public async Task SeedAsync(CancellationToken cancellationToken = default)
730
+ {
731
+ // ... existing seed calls ...
732
+
733
+ await Seed{EntityName}sAsync(cancellationToken); // <-- ADD THIS
734
+
735
+ // ... summary logging ...
736
+ }
737
+
738
+ // 3. Add private seeding method
739
+ private async Task Seed{EntityName}sAsync(CancellationToken cancellationToken)
740
+ {
741
+ _logger.LogInformation("Seeding demo {EntityName}s...");
742
+
743
+ var existingCount = await _context.{EntityName}s.CountAsync(cancellationToken);
744
+ if (existingCount > 0)
745
+ {
746
+ _logger.LogWarning("{EntityName}s already seeded ({Count} exist), skipping.", existingCount);
747
+ return;
748
+ }
749
+
750
+ var createdCount = 0;
751
+
752
+ foreach (var seedItem in {EntityName}SeedData.GetAll{EntityName}s())
753
+ {
754
+ var entity = {EntityName}.Create(
755
+ // Map seedItem properties to factory method parameters
756
+ );
757
+
758
+ // Set deterministic ID (factory generates random GUID)
759
+ typeof({EntityName}).GetProperty("Id")?.SetValue(entity, seedItem.Id);
760
+
761
+ _context.{EntityName}s.Add(entity);
762
+ createdCount++;
763
+ }
764
+
765
+ if (createdCount > 0)
766
+ {
767
+ await _context.SaveChangesAsync(cancellationToken);
768
+ }
769
+
770
+ _logger.LogInformation("Created {Count} demo {EntityName}s.", createdCount);
771
+ }
772
+ ```
773
+
774
+ ### Seed Data GUID Generation
775
+
776
+ ```csharp
777
+ // Option 1: Hardcoded descriptive GUIDs (preferred for small sets)
778
+ public static readonly Guid ProductAlphaId = Guid.Parse("aa000001-0000-0000-0000-000000000001");
779
+ public static readonly Guid ProductBetaId = Guid.Parse("aa000001-0000-0000-0000-000000000002");
780
+
781
+ // Option 2: SHA256-based for larger sets
782
+ private static Guid GenerateEntityGuid(string uniqueKey)
783
+ {
784
+ using var sha256 = System.Security.Cryptography.SHA256.Create();
785
+ var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{EntityName}-{uniqueKey}"));
786
+ return new Guid(hash.Take(16).ToArray());
787
+ }
788
+ ```
789
+
790
+ ### Best Practices
791
+
792
+ | Practice | Description |
793
+ |----------|-------------|
794
+ | Deterministic IDs | NEVER use `Guid.NewGuid()` - seeds must be idempotent |
795
+ | Idempotent | Always check `if exists` before creating |
796
+ | Use factory methods | Call `Entity.Create(...)` not `new Entity()` |
797
+ | Set ID via reflection | Factory methods generate random IDs; override after creation |
798
+ | Realistic data | Use plausible names, descriptions, amounts |
799
+ | Cover all states | Include entities in different statuses (active, closed, etc.) |
800
+ | Reference existing seeds | Use IDs from TenantSeedData, UserSeedData for foreign keys |
801
+ | SaveChanges per batch | Call SaveChanges after each entity group |
802
+
803
+ ---
804
+
608
805
  ## SEED CHECKLIST
609
806
 
610
807
  | Check | Status |
611
808
  |-------|--------|
612
809
  | ☐ Deterministic GUID (not NewGuid) | |
613
- | ☐ 4 languages for each entity | |
810
+ | ☐ 4 languages for each navigation entity | |
614
811
  | ☐ Index translations continue existing sequence | |
615
812
  | ☐ Route aligned with permission path | |
616
813
  | ☐ DisplayOrder consistent | |
@@ -619,6 +816,8 @@ $ORDER = 1
619
816
  | ☐ Resource permissions if sub-resources (Level 5) | |
620
817
  | ☐ Bulk Operations permissions created | |
621
818
  | ☐ RolePermissions assigned | |
819
+ | ☐ Entity SeedData.cs created (if user opted in) | |
820
+ | ☐ DevDataSeeder.cs updated (if user opted in) | |
622
821
 
623
822
  ---
624
823