@atlashub/smartstack-cli 3.5.0 → 3.6.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 (40) hide show
  1. package/dist/index.js +13 -0
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/skills/business-analyse/SKILL.md +26 -15
  5. package/templates/skills/business-analyse/_architecture.md +1 -1
  6. package/templates/skills/business-analyse/_elicitation.md +1 -1
  7. package/templates/skills/business-analyse/_module-loop.md +4 -4
  8. package/templates/skills/business-analyse/html/ba-interactive.html +39 -10
  9. package/templates/skills/business-analyse/questionnaire/06-security.md +1 -1
  10. package/templates/skills/business-analyse/questionnaire.md +2 -2
  11. package/templates/skills/business-analyse/react/components.md +1 -1
  12. package/templates/skills/business-analyse/react/schema.md +1 -1
  13. package/templates/skills/business-analyse/references/html-data-mapping.md +4 -3
  14. package/templates/skills/business-analyse/schemas/feature-schema.json +1 -1
  15. package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +1 -1
  16. package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +1 -0
  17. package/templates/skills/business-analyse/schemas/sections/specification-schema.json +1 -1
  18. package/templates/skills/business-analyse/steps/step-00-init.md +29 -0
  19. package/templates/skills/business-analyse/steps/step-01-cadrage.md +166 -6
  20. package/templates/skills/business-analyse/steps/step-02-decomposition.md +4 -4
  21. package/templates/skills/business-analyse/steps/{step-03a-specify.md → step-03a-data.md} +10 -359
  22. package/templates/skills/business-analyse/steps/step-03b-ui.md +414 -0
  23. package/templates/skills/business-analyse/steps/step-03c-compile.md +343 -0
  24. package/templates/skills/business-analyse/steps/{step-03b-compile.md → step-03d-validate.md} +26 -308
  25. package/templates/skills/business-analyse/steps/step-04-consolidation.md +2 -2
  26. package/templates/skills/business-analyse/steps/step-05a-handoff.md +49 -292
  27. package/templates/skills/business-analyse/steps/step-05b-mapping.md +302 -0
  28. package/templates/skills/business-analyse/steps/step-05c-deploy.md +296 -0
  29. package/templates/skills/business-analyse/steps/step-05d-html.md +326 -0
  30. package/templates/skills/business-analyse/templates/tpl-frd.md +1 -1
  31. package/templates/skills/business-analyse/templates/tpl-launch-displays.md +1 -1
  32. package/templates/skills/business-analyse/templates/tpl-progress.md +1 -1
  33. package/templates/skills/controller/steps/step-03-generate.md +2 -1
  34. package/templates/skills/ralph-loop/SKILL.md +17 -2
  35. package/templates/skills/ralph-loop/references/core-seed-data.md +538 -0
  36. package/templates/skills/ralph-loop/steps/step-00-init.md +2 -0
  37. package/templates/skills/ralph-loop/steps/step-01-task.md +25 -2
  38. package/templates/skills/ralph-loop/steps/step-02-execute.md +39 -15
  39. package/templates/skills/ralph-loop/steps/step-04-check.md +87 -4
  40. package/templates/skills/business-analyse/steps/step-05b-deploy.md +0 -475
@@ -0,0 +1,538 @@
1
+ # Core Seed Data - Execution Reference
2
+
3
+ > **Loaded by:** step-02-execute and step-04 compact loop
4
+ > **Condition:** Infrastructure task involves core seed data or IClientSeedDataProvider
5
+ > **Applies to:** Client projects only (seeding_strategy = "provider", ExtensionsDbContext)
6
+ >
7
+ > **Source of truth:** `/application` skill `templates-seed.md` (lines 608-916)
8
+
9
+ ---
10
+
11
+ ## 1. Parameter Extraction
12
+
13
+ Extract navigation hierarchy from the task's `_seedDataMeta` (populated by guardrail 1e):
14
+
15
+ ```javascript
16
+ const task = currentTask;
17
+ const meta = task._seedDataMeta || task._providerMeta || {};
18
+ const coreSeedData = meta.coreSeedData || {};
19
+
20
+ // Navigation hierarchy
21
+ const navModules = coreSeedData.navigationModules || coreSeedData.navigation || [];
22
+ const permissions = coreSeedData.permissions || [];
23
+ const rolePerms = coreSeedData.rolePermissions || [];
24
+
25
+ // Derived context (from guardrail or PRD)
26
+ const navRoute = meta.navRoute; // e.g. "business.humanresources.projects"
27
+ const contextCode = meta.contextCode; // e.g. "business"
28
+ const appCode = meta.appCode; // e.g. "humanresources"
29
+ const moduleCode = task.module; // e.g. "projects"
30
+
31
+ // If _seedDataMeta is absent, fallback to PRD source
32
+ if (!navRoute) {
33
+ const prd = readJSON('.ralph/prd.json');
34
+ const navRoute = `${prd.source?.context || 'business'}.${prd.source?.application || prd.metadata?.module}.${task.module}`;
35
+ }
36
+ ```
37
+
38
+ **State variables after extraction:**
39
+
40
+ | Variable | Example | Source |
41
+ |----------|---------|--------|
42
+ | `navRoute` | `business.humanresources.projects` | `_seedDataMeta.navRoute` |
43
+ | `contextCode` | `business` | `_seedDataMeta.contextCode` |
44
+ | `appCode` | `humanresources` | `_seedDataMeta.appCode` |
45
+ | `moduleCode` | `projects` | `task.module` |
46
+ | `navModules[]` | `[{code, label, icon, route, translations}]` | `coreSeedData.navigationModules` |
47
+ | `permissions[]` | `[{path, action, description}]` | `coreSeedData.permissions` |
48
+ | `rolePerms[]` | `[{role, permissions[]}]` | `coreSeedData.rolePermissions` |
49
+
50
+ ---
51
+
52
+ ## 2. NavigationModuleSeedData.cs
53
+
54
+ **File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/NavigationModuleSeedData.cs`
55
+
56
+ ### GUID Generation Rule
57
+
58
+ ```csharp
59
+ // NEVER use Guid.NewGuid() — ALWAYS deterministic
60
+ private static Guid GenerateDeterministicGuid(string seed)
61
+ {
62
+ using var sha256 = System.Security.Cryptography.SHA256.Create();
63
+ var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(seed));
64
+ return new Guid(hash.Take(16).ToArray());
65
+ }
66
+
67
+ // Usage: GUIDs are derived from the navigation path
68
+ public static readonly Guid ModuleId = GenerateDeterministicGuid("navigation-module-{navRoute}");
69
+ // Example: GenerateDeterministicGuid("navigation-module-business.humanresources.projects")
70
+ ```
71
+
72
+ ### Template
73
+
74
+ ```csharp
75
+ using SmartStack.Domain.Navigation;
76
+
77
+ namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{ModulePascal};
78
+
79
+ /// <summary>
80
+ /// Navigation seed data for {ModuleLabel} module.
81
+ /// Consumed by IClientSeedDataProvider at application startup.
82
+ /// </summary>
83
+ public static class {ModulePascal}NavigationSeedData
84
+ {
85
+ // Deterministic GUID for this module
86
+ public static readonly Guid {ModulePascal}ModuleId =
87
+ GenerateDeterministicGuid("navigation-module-{navRoute}");
88
+
89
+ /// <summary>
90
+ /// Returns navigation module entry for seeding into core.nav_Modules.
91
+ /// </summary>
92
+ public static NavigationModuleSeedEntry GetModuleEntry(Guid applicationId)
93
+ {
94
+ return new NavigationModuleSeedEntry
95
+ {
96
+ Id = {ModulePascal}ModuleId,
97
+ ApplicationId = applicationId,
98
+ Code = "{moduleCode}",
99
+ Label = "{label_en}",
100
+ Description = "{desc_en}",
101
+ Icon = "{icon}", // Lucide React icon name
102
+ IconType = IconType.Lucide,
103
+ Route = "/{contextCode}/{appCode}/{moduleCode}",
104
+ DisplayOrder = {displayOrder},
105
+ IsActive = true
106
+ };
107
+ }
108
+
109
+ /// <summary>
110
+ /// Returns 4-language translations for this module.
111
+ /// </summary>
112
+ public static IEnumerable<NavigationTranslationSeedEntry> GetTranslationEntries()
113
+ {
114
+ var moduleId = {ModulePascal}ModuleId;
115
+ return new[]
116
+ {
117
+ new NavigationTranslationSeedEntry
118
+ {
119
+ EntityType = NavigationEntityType.Module,
120
+ EntityId = moduleId,
121
+ LanguageCode = "fr",
122
+ Label = "{label_fr}",
123
+ Description = "{desc_fr}"
124
+ },
125
+ new NavigationTranslationSeedEntry
126
+ {
127
+ EntityType = NavigationEntityType.Module,
128
+ EntityId = moduleId,
129
+ LanguageCode = "en",
130
+ Label = "{label_en}",
131
+ Description = "{desc_en}"
132
+ },
133
+ new NavigationTranslationSeedEntry
134
+ {
135
+ EntityType = NavigationEntityType.Module,
136
+ EntityId = moduleId,
137
+ LanguageCode = "it",
138
+ Label = "{label_it}",
139
+ Description = "{desc_it}"
140
+ },
141
+ new NavigationTranslationSeedEntry
142
+ {
143
+ EntityType = NavigationEntityType.Module,
144
+ EntityId = moduleId,
145
+ LanguageCode = "de",
146
+ Label = "{label_de}",
147
+ Description = "{desc_de}"
148
+ }
149
+ };
150
+ }
151
+
152
+ private static Guid GenerateDeterministicGuid(string seed)
153
+ {
154
+ using var sha256 = System.Security.Cryptography.SHA256.Create();
155
+ var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(seed));
156
+ return new Guid(hash.Take(16).ToArray());
157
+ }
158
+ }
159
+
160
+ /// <summary>Seed entry DTO for navigation module.</summary>
161
+ public class NavigationModuleSeedEntry
162
+ {
163
+ public Guid Id { get; init; }
164
+ public Guid ApplicationId { get; init; }
165
+ public string Code { get; init; } = null!;
166
+ public string Label { get; init; } = null!;
167
+ public string Description { get; init; } = null!;
168
+ public string Icon { get; init; } = null!;
169
+ public IconType IconType { get; init; }
170
+ public string Route { get; init; } = null!;
171
+ public int DisplayOrder { get; init; }
172
+ public bool IsActive { get; init; }
173
+ }
174
+
175
+ /// <summary>Seed entry DTO for navigation translation.</summary>
176
+ public class NavigationTranslationSeedEntry
177
+ {
178
+ public NavigationEntityType EntityType { get; init; }
179
+ public Guid EntityId { get; init; }
180
+ public string LanguageCode { get; init; } = null!;
181
+ public string Label { get; init; } = null!;
182
+ public string Description { get; init; } = null!;
183
+ }
184
+ ```
185
+
186
+ **Replace placeholders** with values from `navModules[]` and PRD `project` metadata.
187
+
188
+ ---
189
+
190
+ ## 3. PermissionsSeedData.cs — MCP-First
191
+
192
+ ### Step A: Call MCP (PRIMARY)
193
+
194
+ ```
195
+ Tool: mcp__smartstack__generate_permissions
196
+ Args:
197
+ navRoute: "{navRoute}"
198
+ includeStandardActions: true
199
+ includeWildcard: true
200
+ ```
201
+
202
+ MCP returns:
203
+ - `Permissions.cs` nested class (Application layer constants)
204
+ - Permission seed entries with deterministic GUIDs
205
+
206
+ ### Step B: Write Permissions.cs (Application layer)
207
+
208
+ ```csharp
209
+ // Add to Application/Common/Authorization/Permissions.cs
210
+ public static class {ContextPascal}
211
+ {
212
+ public const string Access = "{contextCode}.{appCode}";
213
+
214
+ public static class {ModulePascal}
215
+ {
216
+ public const string View = "{navRoute}.read";
217
+ public const string Create = "{navRoute}.create";
218
+ public const string Update = "{navRoute}.update";
219
+ public const string Delete = "{navRoute}.delete";
220
+ }
221
+ }
222
+ ```
223
+
224
+ ### Step C: Write PermissionsSeedData.cs (Infrastructure layer)
225
+
226
+ **File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/PermissionsSeedData.cs`
227
+
228
+ ```csharp
229
+ namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{ModulePascal};
230
+
231
+ /// <summary>
232
+ /// Permission seed data for {ModuleLabel} module.
233
+ /// Consumed by IClientSeedDataProvider at application startup.
234
+ /// </summary>
235
+ public static class {ModulePascal}PermissionsSeedData
236
+ {
237
+ // Deterministic GUIDs for permissions
238
+ public static readonly Guid WildcardPermId = GenerateGuid("{navRoute}.*");
239
+ public static readonly Guid ReadPermId = GenerateGuid("{navRoute}.read");
240
+ public static readonly Guid CreatePermId = GenerateGuid("{navRoute}.create");
241
+ public static readonly Guid UpdatePermId = GenerateGuid("{navRoute}.update");
242
+ public static readonly Guid DeletePermId = GenerateGuid("{navRoute}.delete");
243
+
244
+ public static IEnumerable<PermissionSeedEntry> GetPermissionEntries(Guid moduleId)
245
+ {
246
+ return new[]
247
+ {
248
+ new PermissionSeedEntry { Id = WildcardPermId, Path = "{navRoute}.*", Level = PermissionLevel.Module, Action = PermissionAction.Access, IsWildcard = true, ModuleId = moduleId, Description = "Full {moduleLabel} access" },
249
+ new PermissionSeedEntry { Id = ReadPermId, Path = "{navRoute}.read", Level = PermissionLevel.Module, Action = PermissionAction.Read, IsWildcard = false, ModuleId = moduleId, Description = "View {moduleLabel}" },
250
+ new PermissionSeedEntry { Id = CreatePermId, Path = "{navRoute}.create", Level = PermissionLevel.Module, Action = PermissionAction.Create, IsWildcard = false, ModuleId = moduleId, Description = "Create {moduleLabel}" },
251
+ new PermissionSeedEntry { Id = UpdatePermId, Path = "{navRoute}.update", Level = PermissionLevel.Module, Action = PermissionAction.Update, IsWildcard = false, ModuleId = moduleId, Description = "Update {moduleLabel}" },
252
+ new PermissionSeedEntry { Id = DeletePermId, Path = "{navRoute}.delete", Level = PermissionLevel.Module, Action = PermissionAction.Delete, IsWildcard = false, ModuleId = moduleId, Description = "Delete {moduleLabel}" }
253
+ };
254
+ }
255
+
256
+ private static Guid GenerateGuid(string path)
257
+ {
258
+ using var sha256 = System.Security.Cryptography.SHA256.Create();
259
+ var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"permission-{path}"));
260
+ return new Guid(hash.Take(16).ToArray());
261
+ }
262
+ }
263
+
264
+ public class PermissionSeedEntry
265
+ {
266
+ public Guid Id { get; init; }
267
+ public string Path { get; init; } = null!;
268
+ public PermissionLevel Level { get; init; }
269
+ public PermissionAction Action { get; init; }
270
+ public bool IsWildcard { get; init; }
271
+ public Guid ModuleId { get; init; }
272
+ public string Description { get; init; } = null!;
273
+ }
274
+ ```
275
+
276
+ ### Step D: MCP Fallback
277
+
278
+ If MCP `generate_permissions` fails, use the template above directly with values derived from the PRD `coreSeedData.permissions[]`.
279
+
280
+ ---
281
+
282
+ ## 4. RolesSeedData.cs
283
+
284
+ **File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/RolesSeedData.cs`
285
+
286
+ ### Context-Based Role Mapping
287
+
288
+ | Context | Admin | Manager | Contributor | Viewer |
289
+ |---------|-------|---------|-------------|--------|
290
+ | `platform.*` | CRUD | CRU | CR | R |
291
+ | `business.*` | CRUD | CRU | CR | R |
292
+ | `personal.*` | CRUD | CRU | CR | R |
293
+
294
+ ### Template
295
+
296
+ ```csharp
297
+ namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{ModulePascal};
298
+
299
+ /// <summary>
300
+ /// Role-permission mapping seed data for {ModuleLabel} module.
301
+ /// Maps permissions to application-scoped roles (Admin, Manager, Contributor, Viewer).
302
+ /// Consumed by IClientSeedDataProvider at application startup.
303
+ /// </summary>
304
+ public static class {ModulePascal}RolesSeedData
305
+ {
306
+ /// <summary>
307
+ /// Returns role-permission mappings for this module.
308
+ /// Roles are resolved at runtime by Code (not hardcoded GUIDs).
309
+ /// </summary>
310
+ public static IEnumerable<RolePermissionSeedEntry> GetRolePermissionEntries()
311
+ {
312
+ // Admin: wildcard access
313
+ yield return new RolePermissionSeedEntry { RoleCode = "admin", PermissionPath = "{navRoute}.*" };
314
+
315
+ // Manager: CRUD
316
+ yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.read" };
317
+ yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.create" };
318
+ yield return new RolePermissionSeedEntry { RoleCode = "manager", PermissionPath = "{navRoute}.update" };
319
+
320
+ // Contributor: CR
321
+ yield return new RolePermissionSeedEntry { RoleCode = "contributor", PermissionPath = "{navRoute}.read" };
322
+ yield return new RolePermissionSeedEntry { RoleCode = "contributor", PermissionPath = "{navRoute}.create" };
323
+
324
+ // Viewer: R
325
+ yield return new RolePermissionSeedEntry { RoleCode = "viewer", PermissionPath = "{navRoute}.read" };
326
+ }
327
+ }
328
+
329
+ public class RolePermissionSeedEntry
330
+ {
331
+ public string RoleCode { get; init; } = null!;
332
+ public string PermissionPath { get; init; } = null!;
333
+ }
334
+ ```
335
+
336
+ ---
337
+
338
+ ## 5. IClientSeedDataProvider Implementation
339
+
340
+ **File:** `Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`
341
+
342
+ ### Critical Rules
343
+
344
+ | Rule | Description |
345
+ |------|-------------|
346
+ | Factory methods | `NavigationModule.Create(...)`, `Permission.CreateForModule(...)`, `RolePermission.Create(...)` — NEVER `new Entity()` |
347
+ | Idempotence | Each Seed method checks existence before inserting |
348
+ | SaveChanges per group | Navigation -> save -> Permissions -> save -> RolePermissions -> save |
349
+ | FK resolution by Code | Parent entities found by `Code`, not hardcoded GUID |
350
+ | DI registration | `services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()` |
351
+
352
+ ### Template
353
+
354
+ ```csharp
355
+ using Microsoft.EntityFrameworkCore;
356
+ using SmartStack.Application.Common.Interfaces;
357
+ using SmartStack.Application.Common.Interfaces.Seeding;
358
+ using SmartStack.Domain.Navigation;
359
+ using SmartStack.Domain.Platform.Administration.Roles;
360
+ using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{Module1Pascal};
361
+ // using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{Module2Pascal}; // Add per module
362
+
363
+ namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;
364
+
365
+ /// <summary>
366
+ /// Seeds {AppLabel} navigation, permissions, and role-permission data
367
+ /// into the SmartStack Core schema at application startup.
368
+ /// </summary>
369
+ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
370
+ {
371
+ public int Order => 100;
372
+
373
+ public async Task SeedNavigationAsync(ICoreDbContext context, CancellationToken ct)
374
+ {
375
+ // --- Application ---
376
+ var exists = await context.NavigationApplications
377
+ .AnyAsync(a => a.Code == "{appCode}", ct);
378
+ if (exists) return;
379
+
380
+ var parentContext = await context.NavigationContexts
381
+ .FirstAsync(c => c.Code == "{contextCode}", ct);
382
+
383
+ var app = NavigationApplication.Create(
384
+ parentContext.Id, "{appCode}", "{appLabel_en}",
385
+ "{appDesc_en}", "{appIcon}", IconType.Lucide,
386
+ "/{contextCode}/{appCode}", 1);
387
+ context.NavigationApplications.Add(app);
388
+ await ((DbContext)context).SaveChangesAsync(ct);
389
+
390
+ // --- Application translations (4 languages) ---
391
+ var appTranslations = new[]
392
+ {
393
+ NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "fr", "{appLabel_fr}", "{appDesc_fr}"),
394
+ NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "en", "{appLabel_en}", "{appDesc_en}"),
395
+ NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "it", "{appLabel_it}", "{appDesc_it}"),
396
+ NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "de", "{appLabel_de}", "{appDesc_de}")
397
+ };
398
+ foreach (var t in appTranslations) context.NavigationTranslations.Add(t);
399
+ await ((DbContext)context).SaveChangesAsync(ct);
400
+
401
+ // --- Modules ---
402
+ // Module: {Module1}
403
+ var mod1Entry = {Module1Pascal}NavigationSeedData.GetModuleEntry(app.Id);
404
+ var mod1 = NavigationModule.Create(
405
+ mod1Entry.ApplicationId, mod1Entry.Code, mod1Entry.Label,
406
+ mod1Entry.Description, mod1Entry.Icon, mod1Entry.IconType,
407
+ mod1Entry.Route, mod1Entry.DisplayOrder);
408
+ context.NavigationModules.Add(mod1);
409
+
410
+ // Repeat for each module...
411
+ await ((DbContext)context).SaveChangesAsync(ct);
412
+
413
+ // --- Module translations ---
414
+ foreach (var t in {Module1Pascal}NavigationSeedData.GetTranslationEntries())
415
+ {
416
+ context.NavigationTranslations.Add(
417
+ NavigationTranslation.Create(t.EntityType, t.EntityId, t.LanguageCode, t.Label, t.Description));
418
+ }
419
+ // Repeat for each module...
420
+ await ((DbContext)context).SaveChangesAsync(ct);
421
+ }
422
+
423
+ public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
424
+ {
425
+ var exists = await context.Permissions
426
+ .AnyAsync(p => p.Path == "{contextCode}.{appCode}.*", ct);
427
+ if (exists) return;
428
+
429
+ // Application-level wildcard
430
+ var appWildcard = Permission.CreateWildcard(
431
+ "{contextCode}.{appCode}.*", PermissionLevel.Application,
432
+ "Full {appLabel_en} access");
433
+ context.Permissions.Add(appWildcard);
434
+
435
+ // Module permissions
436
+ var mod1 = await context.NavigationModules
437
+ .FirstAsync(m => m.Code == "{module1Code}", ct);
438
+ foreach (var entry in {Module1Pascal}PermissionsSeedData.GetPermissionEntries(mod1.Id))
439
+ {
440
+ var perm = entry.IsWildcard
441
+ ? Permission.CreateWildcard(entry.Path, entry.Level, entry.Description)
442
+ : Permission.CreateForModule(entry.Path, entry.Level, entry.Action, false, entry.ModuleId, entry.Description);
443
+ context.Permissions.Add(perm);
444
+ }
445
+
446
+ // Repeat for each module...
447
+ await ((DbContext)context).SaveChangesAsync(ct);
448
+ }
449
+
450
+ public async Task SeedRolePermissionsAsync(ICoreDbContext context, CancellationToken ct)
451
+ {
452
+ var exists = await context.RolePermissions
453
+ .AnyAsync(rp => rp.Permission!.Path.StartsWith("{contextCode}.{appCode}."), ct);
454
+ if (exists) return;
455
+
456
+ // Resolve roles by Code
457
+ var roles = await context.Roles
458
+ .Where(r => r.ApplicationId != null || r.IsSystem)
459
+ .ToListAsync(ct);
460
+
461
+ // Resolve permissions
462
+ var permissions = await context.Permissions
463
+ .Where(p => p.Path.StartsWith("{contextCode}.{appCode}."))
464
+ .ToListAsync(ct);
465
+
466
+ // Apply role-permission mappings from all modules
467
+ var allMappings = new List<RolePermissionSeedEntry>();
468
+ allMappings.AddRange({Module1Pascal}RolesSeedData.GetRolePermissionEntries());
469
+ // allMappings.AddRange({Module2Pascal}RolesSeedData.GetRolePermissionEntries()); // per module
470
+
471
+ foreach (var mapping in allMappings)
472
+ {
473
+ var role = roles.FirstOrDefault(r => r.Code == mapping.RoleCode);
474
+ var perm = permissions.FirstOrDefault(p => p.Path == mapping.PermissionPath);
475
+ if (role != null && perm != null)
476
+ {
477
+ context.RolePermissions.Add(RolePermission.Create(role.Id, perm.Id, "system"));
478
+ }
479
+ }
480
+
481
+ await ((DbContext)context).SaveChangesAsync(ct);
482
+ }
483
+ }
484
+ ```
485
+
486
+ ### DI Registration
487
+
488
+ ```csharp
489
+ // In Infrastructure/DependencyInjection.cs — add:
490
+ using SmartStack.Application.Common.Interfaces.Seeding;
491
+
492
+ // In the registration method:
493
+ services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
494
+ ```
495
+
496
+ ---
497
+
498
+ ## 6. Multi-Module Handling
499
+
500
+ When processing multiple modules in the same ralph-loop run:
501
+
502
+ ### Module 1 (first): Creates everything from scratch
503
+
504
+ 1. `{Module1}NavigationSeedData.cs`
505
+ 2. `{Module1}PermissionsSeedData.cs`
506
+ 3. `{Module1}RolesSeedData.cs`
507
+ 4. `{AppPascalName}SeedDataProvider.cs` (new)
508
+ 5. DI registration (new)
509
+
510
+ ### Module 2+ (subsequent): Append to existing provider
511
+
512
+ 1. `{Module2}NavigationSeedData.cs` (new file)
513
+ 2. `{Module2}PermissionsSeedData.cs` (new file)
514
+ 3. `{Module2}RolesSeedData.cs` (new file)
515
+ 4. `{AppPascalName}SeedDataProvider.cs` (**modify** — add using, add entries in each Seed method)
516
+ 5. DI registration (already exists — skip)
517
+
518
+ **Detection:** Check if `{AppPascalName}SeedDataProvider.cs` exists. If yes, READ it and ADD the new module's entries to each of the 3 methods.
519
+
520
+ ---
521
+
522
+ ## 7. Verification Checklist (BLOCKING)
523
+
524
+ Before marking the task as completed, verify ALL:
525
+
526
+ - [ ] Deterministic GUIDs (NEVER `Guid.NewGuid()`) — SHA256 of path
527
+ - [ ] 4 languages for each navigation entity (fr, en, it, de)
528
+ - [ ] `Permissions.cs` constants match seed data paths
529
+ - [ ] MCP `generate_permissions` called (or fallback used)
530
+ - [ ] Role-permission mappings assigned (Admin, Manager, Contributor, Viewer)
531
+ - [ ] `IClientSeedDataProvider` generated with 3 methods
532
+ - [ ] Each Seed method is idempotent (checks existence before inserting)
533
+ - [ ] Factory methods used throughout (NEVER `new Entity()`)
534
+ - [ ] `SaveChangesAsync` called per group (Navigation → Permissions → RolePermissions)
535
+ - [ ] DI registration added: `services.AddScoped<IClientSeedDataProvider, ...>()`
536
+ - [ ] `dotnet build` passes after generation
537
+
538
+ **If ANY check fails, the task status = 'failed'.**
@@ -14,6 +14,8 @@ next_step: steps/step-01-task.md
14
14
  - YOU ARE AN INITIALIZER, not an executor
15
15
  - FORBIDDEN to load step-01 until init is complete
16
16
  - ALWAYS check prd.json version is v2
17
+ - **CONTEXT BUDGET**: Keep init output COMPACT. Do NOT dump verbose MCP responses or long file listings. Every token saved here = more tokens for actual code generation in the COMPACT LOOP.
18
+ - **NEVER DELEGATE**: Do NOT use the Task tool to delegate the Ralph loop to a sub-agent. The loop MUST run in the main agent context. Sub-agents lose skill context and stop prematurely.
17
19
 
18
20
  ## YOUR TASK:
19
21
 
@@ -499,7 +499,19 @@ function transformPrdJsonToRalphV2(prdJson, moduleCode) {
499
499
  "src/Infrastructure/Persistence/Seeding/Data/SeedConstants.cs"
500
500
  ], modified: [] },
501
501
  validation: null, error: null, module: moduleCode,
502
- _seedDataMeta: { source: "guardrail-derived", coreSeedData }
502
+ _seedDataMeta: {
503
+ source: "guardrail-derived",
504
+ coreSeedData,
505
+ navRoute: prdJson.project?.navRoute
506
+ || (permissions?.[0]?.path?.split('.').slice(0, -1).join('.'))
507
+ || `business.${prdJson.project?.application || 'app'}.${moduleCode}`,
508
+ contextCode: prdJson.project?.context
509
+ || coreSeedData.navigationModules?.[0]?.route?.split('/')?.[1]
510
+ || 'business',
511
+ appCode: prdJson.project?.application
512
+ || coreSeedData.navigationModules?.[0]?.route?.split('/')?.[2],
513
+ appLabels: prdJson.project?.labels || null
514
+ }
503
515
  });
504
516
  lastIdByCategory["infrastructure"] = taskId;
505
517
  taskId++;
@@ -530,7 +542,18 @@ function transformPrdJsonToRalphV2(prdJson, moduleCode) {
530
542
  ].join("; "),
531
543
  started_at: null, completed_at: null, iteration: null, commit_hash: null,
532
544
  files_changed: { created: ["src/Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs"], modified: ["src/Infrastructure/DependencyInjection.cs"] },
533
- validation: null, error: null, module: moduleCode
545
+ validation: null, error: null, module: moduleCode,
546
+ _providerMeta: {
547
+ consumesSeedDataFiles: [
548
+ `Infrastructure/Persistence/Seeding/Data/${moduleCode}/NavigationModuleSeedData.cs`,
549
+ `Infrastructure/Persistence/Seeding/Data/${moduleCode}/PermissionsSeedData.cs`,
550
+ `Infrastructure/Persistence/Seeding/Data/${moduleCode}/RolesSeedData.cs`
551
+ ],
552
+ navRoute: prdJson.project?.navRoute
553
+ || `business.${prdJson.project?.application || 'app'}.${moduleCode}`,
554
+ contextCode: prdJson.project?.context || 'business',
555
+ appCode: prdJson.project?.application
556
+ }
534
557
  });
535
558
  lastIdByCategory["infrastructure"] = taskId;
536
559
  taskId++;
@@ -435,15 +435,30 @@ A validation task is ONLY complete when:
435
435
 
436
436
  When executing `infrastructure` category tasks, follow these rules strictly:
437
437
 
438
- 1. **Seed data files** MUST go under `Infrastructure/Persistence/Seeding/Data/{Module}/`
438
+ 1. **Core seed data (navigation, permissions, roles) CONDITIONAL REFERENCE LOADING:**
439
+
440
+ > **IF** the current task description contains "seed data", "SeedData",
441
+ > "NavigationModule", "PermissionsSeedData", "RolesSeedData", or "IClientSeedDataProvider":
442
+ > **THEN read `references/core-seed-data.md`** for COMPLETE execution guidance including:
443
+ > - Parameter extraction from PRD seedDataCore / task `_seedDataMeta`
444
+ > - MCP `generate_permissions` call sequence (primary) with fallback templates
445
+ > - `NavigationModuleSeedData.cs` template (deterministic GUIDs, 4 languages)
446
+ > - `PermissionsSeedData.cs` template (wraps MCP output)
447
+ > - `RolesSeedData.cs` template (context-based role mapping)
448
+ > - `IClientSeedDataProvider` complete implementation template
449
+ > - DI registration pattern
450
+ > - Verification checklist (BLOCKING)
451
+ >
452
+ > **This reference is MANDATORY for seed data tasks. Do NOT improvise.**
453
+
454
+ **Directory convention:** All seed data files MUST go under `Infrastructure/Persistence/Seeding/Data/{Module}/`
439
455
  - NEVER use `Infrastructure/Data/SeedData/` or `Data/SeedData/`
440
- - Each module gets 5 core files: NavigationModuleSeedData.cs, PermissionsSeedData.cs, RolesSeedData.cs, TenantSeedData.cs, UserSeedData.cs
441
456
 
442
457
  2. **IClientSeedDataProvider** (client projects with `extensions` DbContext):
443
458
  - Generate at `Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`
444
459
  - Register in DI: `services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()`
445
460
  - Must implement: SeedNavigationAsync, SeedPermissionsAsync, SeedRolePermissionsAsync
446
- - See `/application` skill step-03b-provider for the full pattern
461
+ - **Complete template and rules in `references/core-seed-data.md`**
447
462
 
448
463
  3. **DevDataSeeder** for domain entity seed data:
449
464
  - Located at `Infrastructure/Persistence/Seeding/DevDataSeeder.cs`
@@ -514,15 +529,29 @@ Only fr/en translations → MUST have 4 languages (fr, en,
514
529
  | `business.*` | `BusinessLayout` | `/business` |
515
530
  | `personal.*` | `UserLayout` | `/personal/myspace` |
516
531
 
517
- **API/Controller task guidance (MANDATORY folder hierarchy):**
532
+ **Backend folder hierarchy (MANDATORY for ALL layers):**
518
533
 
519
- When executing `api` category tasks, follow these rules strictly:
534
+ > **CRITICAL:** ALL backend files MUST be organized by `{ContextPascal}/{ApplicationName}/{ModuleName}/` hierarchy.
535
+ > Derive from PRD `project.application` and feature.json `metadata.context`.
536
+ > The file paths in prd.json already include this hierarchy — follow them exactly.
520
537
 
521
- 1. **Controllers MUST be organized by context and application folders:**
522
- - Path: `Api/Controllers/{ContextShort}/{Application}/{EntityName}Controller.cs`
523
- - If no application sub-level: `Api/Controllers/{ContextShort}/{EntityName}Controller.cs`
538
+ When executing `domain`, `application`, `infrastructure`, `api`, or `test` category tasks:
524
539
 
525
- 2. **Context-to-folder mapping (from navRoute):**
540
+ 1. **ALL layers use context/application/module folder hierarchy:**
541
+
542
+ | Layer | Path Pattern |
543
+ |-------|-------------|
544
+ | Domain | `Domain/Entities/{ContextPascal}/{App}/{Module}/{Entity}.cs` |
545
+ | Domain Enums | `Domain/Enums/{ContextPascal}/{App}/{Module}/{Enum}.cs` |
546
+ | Domain Exceptions | `Domain/Exceptions/{ContextPascal}/{App}/{Module}/{Exception}.cs` |
547
+ | Application Services | `Application/Services/{ContextPascal}/{App}/{Module}/{Service}.cs` |
548
+ | Application DTOs | `Application/DTOs/{ContextPascal}/{App}/{Module}/{Dto}.cs` |
549
+ | Application Validators | `Application/Validators/{ContextPascal}/{App}/{Module}/{Validator}.cs` |
550
+ | Infrastructure Configs | `Infrastructure/Persistence/Configurations/{ContextPascal}/{App}/{Module}/{Config}.cs` |
551
+ | API Controllers | `Api/Controllers/{ContextShort}/{App}/{Entity}Controller.cs` |
552
+ | Tests | `Tests/Unit/Domain/{ContextPascal}/{App}/{Module}/{Tests}.cs` |
553
+
554
+ 2. **Controller context-to-folder mapping (`{ContextShort}`):**
526
555
 
527
556
  | NavRoute Prefix | Controller Folder |
528
557
  |-----------------|-------------------|
@@ -531,12 +560,7 @@ When executing `api` category tasks, follow these rules strictly:
531
560
  | `business.*` | `Business` |
532
561
  | `personal.*` | `User` |
533
562
 
534
- 3. **Sub-folders for applications/modules with multiple controllers:**
535
- - `Admin/Tenants/` for tenant-related controllers
536
- - `Admin/AI/` for AI-related controllers
537
- - `Admin/Communications/` for communication controllers
538
-
539
- 4. **Reference:** SmartStack.app `Api/Controllers/` for the canonical structure
563
+ 3. **Reference:** SmartStack.app `Api/Controllers/` for the canonical structure
540
564
 
541
565
  ### 4. Explore Context (if needed)
542
566