@atlashub/smartstack-cli 3.21.0 → 3.23.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 (36) hide show
  1. package/dist/index.js +17 -5
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +155 -162
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/skills/apex/SKILL.md +21 -0
  7. package/templates/skills/apex/references/smartstack-api.md +481 -0
  8. package/templates/skills/apex/references/smartstack-layers.md +85 -15
  9. package/templates/skills/apex/steps/step-00-init.md +27 -14
  10. package/templates/skills/apex/steps/step-01-analyze.md +18 -0
  11. package/templates/skills/apex/steps/step-03-execute.md +8 -6
  12. package/templates/skills/apex/steps/step-04-validate.md +92 -0
  13. package/templates/skills/apex/steps/step-07-tests.md +29 -5
  14. package/templates/skills/application/references/application-roles-template.md +2 -2
  15. package/templates/skills/application/steps/step-05-frontend.md +40 -35
  16. package/templates/skills/application/templates-frontend.md +64 -36
  17. package/templates/skills/business-analyse/html/ba-interactive.html +80 -6
  18. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +38 -6
  19. package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +42 -0
  20. package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -0
  21. package/templates/skills/business-analyse/references/deploy-data-build.md +5 -3
  22. package/templates/skills/business-analyse/references/handoff-file-templates.md +2 -1
  23. package/templates/skills/business-analyse/references/naming-conventions.md +245 -0
  24. package/templates/skills/business-analyse/references/validate-incremental-html.md +26 -4
  25. package/templates/skills/business-analyse/references/validation-checklist.md +31 -11
  26. package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -0
  27. package/templates/skills/business-analyse/steps/step-03b-ui.md +59 -0
  28. package/templates/skills/business-analyse/steps/step-03c-compile.md +114 -0
  29. package/templates/skills/business-analyse/steps/step-03d-validate.md +144 -22
  30. package/templates/skills/business-analyse/steps/step-05a-handoff.md +114 -2
  31. package/templates/skills/business-analyse/steps/step-05b-deploy.md +28 -0
  32. package/templates/skills/ralph-loop/references/category-rules.md +5 -2
  33. package/templates/skills/ralph-loop/references/compact-loop.md +52 -1
  34. package/templates/skills/ralph-loop/references/core-seed-data.md +232 -21
  35. package/templates/skills/ralph-loop/steps/step-01-task.md +36 -4
  36. package/templates/skills/ralph-loop/steps/step-02-execute.md +81 -0
@@ -53,6 +53,171 @@ if (!navRoute) {
53
53
 
54
54
  ---
55
55
 
56
+ ## 1b. NavigationApplicationSeedData.cs (ONCE per application)
57
+
58
+ **File:** `Infrastructure/Persistence/Seeding/Data/NavigationApplicationSeedData.cs`
59
+
60
+ > **MANDATORY:** This file MUST be created BEFORE any module seed data.
61
+ > Without it, modules have no parent ApplicationId and ApplicationRolesSeedData has no GUID reference.
62
+ > This file is created **ONCE per application** (not per module).
63
+
64
+ ### Data Source
65
+
66
+ From `seedDataCore.navigationApplications[0]` in feature.json (generated by BA step-05a):
67
+
68
+ | Placeholder | Source |
69
+ |-------------|--------|
70
+ | `{appCode}` | `navigationApplications[0].code` |
71
+ | `{appLabel_xx}` | `navigationApplications[0].labels.xx` (fr, en, it, de) |
72
+ | `{appDesc_xx}` | `navigationApplications[0].description.xx` |
73
+ | `{appIcon}` | `navigationApplications[0].icon` |
74
+ | `{contextCode}` | `navigationApplications[0].context` or `_seedDataMeta.contextCode` |
75
+
76
+ ### GUID Generation Rule
77
+
78
+ ```csharp
79
+ // Deterministic GUID from context + application code
80
+ public static readonly Guid ApplicationId =
81
+ GenerateDeterministicGuid("navigation-application-{contextCode}.{appCode}");
82
+ // Example: GenerateDeterministicGuid("navigation-application-business.humanresources")
83
+ ```
84
+
85
+ ### Template
86
+
87
+ ```csharp
88
+ using SmartStack.Domain.Navigation;
89
+
90
+ namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;
91
+
92
+ /// <summary>
93
+ /// Navigation seed data for {AppLabel_en} application.
94
+ /// Consumed by IClientSeedDataProvider at application startup.
95
+ /// Created ONCE per application — modules reference ApplicationId as parent.
96
+ /// </summary>
97
+ public static class NavigationApplicationSeedData
98
+ {
99
+ // Deterministic GUID for this application
100
+ public static readonly Guid ApplicationId =
101
+ GenerateDeterministicGuid("navigation-application-{contextCode}.{appCode}");
102
+
103
+ /// <summary>
104
+ /// Returns navigation application entry for seeding into core.nav_Applications.
105
+ /// </summary>
106
+ public static NavigationApplicationSeedEntry GetApplicationEntry(Guid contextId)
107
+ {
108
+ return new NavigationApplicationSeedEntry
109
+ {
110
+ Id = ApplicationId,
111
+ ContextId = contextId,
112
+ Code = "{appCode}",
113
+ Label = "{appLabel_en}",
114
+ Description = "{appDesc_en}",
115
+ Icon = "{appIcon}", // Lucide React icon name
116
+ IconType = IconType.Lucide,
117
+ Route = ToKebabCase("/{contextCode}/{appCode}"),
118
+ DisplayOrder = 1,
119
+ IsActive = true
120
+ };
121
+ }
122
+
123
+ /// <summary>
124
+ /// Returns 4-language translations for this application.
125
+ /// </summary>
126
+ public static IEnumerable<NavigationTranslationSeedEntry> GetTranslationEntries()
127
+ {
128
+ var appId = ApplicationId;
129
+ return new[]
130
+ {
131
+ new NavigationTranslationSeedEntry
132
+ {
133
+ EntityType = NavigationEntityType.Application,
134
+ EntityId = appId,
135
+ LanguageCode = "fr",
136
+ Label = "{appLabel_fr}",
137
+ Description = "{appDesc_fr}"
138
+ },
139
+ new NavigationTranslationSeedEntry
140
+ {
141
+ EntityType = NavigationEntityType.Application,
142
+ EntityId = appId,
143
+ LanguageCode = "en",
144
+ Label = "{appLabel_en}",
145
+ Description = "{appDesc_en}"
146
+ },
147
+ new NavigationTranslationSeedEntry
148
+ {
149
+ EntityType = NavigationEntityType.Application,
150
+ EntityId = appId,
151
+ LanguageCode = "it",
152
+ Label = "{appLabel_it}",
153
+ Description = "{appDesc_it}"
154
+ },
155
+ new NavigationTranslationSeedEntry
156
+ {
157
+ EntityType = NavigationEntityType.Application,
158
+ EntityId = appId,
159
+ LanguageCode = "de",
160
+ Label = "{appLabel_de}",
161
+ Description = "{appDesc_de}"
162
+ }
163
+ };
164
+ }
165
+
166
+ private static Guid GenerateDeterministicGuid(string seed)
167
+ {
168
+ using var sha256 = System.Security.Cryptography.SHA256.Create();
169
+ var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(seed));
170
+ return new Guid(hash.Take(16).ToArray());
171
+ }
172
+
173
+ /// <summary>
174
+ /// Converts PascalCase route segments to kebab-case for web URLs.
175
+ /// </summary>
176
+ private static string ToKebabCase(string route)
177
+ {
178
+ if (string.IsNullOrEmpty(route)) return route;
179
+
180
+ var segments = route.Split('/');
181
+ var kebabSegments = new List<string>();
182
+
183
+ foreach (var segment in segments)
184
+ {
185
+ if (string.IsNullOrEmpty(segment))
186
+ {
187
+ kebabSegments.Add(segment);
188
+ continue;
189
+ }
190
+
191
+ var kebab = System.Text.RegularExpressions.Regex
192
+ .Replace(segment, "([a-z])([A-Z])", "$1-$2")
193
+ .ToLowerInvariant();
194
+ kebabSegments.Add(kebab);
195
+ }
196
+
197
+ return string.Join("/", kebabSegments);
198
+ }
199
+ }
200
+
201
+ /// <summary>Seed entry DTO for navigation application.</summary>
202
+ public class NavigationApplicationSeedEntry
203
+ {
204
+ public Guid Id { get; init; }
205
+ public Guid ContextId { get; init; }
206
+ public string Code { get; init; } = null!;
207
+ public string Label { get; init; } = null!;
208
+ public string? Description { get; init; }
209
+ public string? Icon { get; init; }
210
+ public IconType IconType { get; init; }
211
+ public string? Route { get; init; }
212
+ public int DisplayOrder { get; init; }
213
+ public bool IsActive { get; init; }
214
+ }
215
+ ```
216
+
217
+ **Replace placeholders** with values from `seedDataCore.navigationApplications[0]`.
218
+
219
+ ---
220
+
56
221
  ## 2. NavigationModuleSeedData.cs
57
222
 
58
223
  **File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/NavigationModuleSeedData.cs`
@@ -104,7 +269,7 @@ public static class {ModulePascal}NavigationSeedData
104
269
  Description = "{desc_en}",
105
270
  Icon = "{icon}", // Lucide React icon name
106
271
  IconType = IconType.Lucide,
107
- Route = "/{contextCode}/{appCode}/{moduleCode}",
272
+ Route = ToKebabCase($"/{contextCode}/{appCode}/{moduleCode}"),
108
273
  DisplayOrder = {displayOrder},
109
274
  IsActive = true
110
275
  };
@@ -159,6 +324,35 @@ public static class {ModulePascal}NavigationSeedData
159
324
  var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(seed));
160
325
  return new Guid(hash.Take(16).ToArray());
161
326
  }
327
+
328
+ /// <summary>
329
+ /// Converts PascalCase route segments to kebab-case for web URLs.
330
+ /// Example: /business/HumanResources/TimeManagement → /business/human-resources/time-management
331
+ /// </summary>
332
+ private static string ToKebabCase(string route)
333
+ {
334
+ if (string.IsNullOrEmpty(route)) return route;
335
+
336
+ var segments = route.Split('/');
337
+ var kebabSegments = new List<string>();
338
+
339
+ foreach (var segment in segments)
340
+ {
341
+ if (string.IsNullOrEmpty(segment))
342
+ {
343
+ kebabSegments.Add(segment);
344
+ continue;
345
+ }
346
+
347
+ // Convert PascalCase to kebab-case: HumanResources → human-resources
348
+ var kebab = System.Text.RegularExpressions.Regex
349
+ .Replace(segment, "([a-z])([A-Z])", "$1-$2")
350
+ .ToLowerInvariant();
351
+ kebabSegments.Add(kebab);
352
+ }
353
+
354
+ return string.Join("/", kebabSegments);
355
+ }
162
356
  }
163
357
 
164
358
  /// <summary>Seed entry DTO for navigation module.</summary>
@@ -236,7 +430,7 @@ public static IEnumerable<NavigationSectionSeedEntry> GetSectionEntries(Guid mod
236
430
  Description = "{section1_desc_en}",
237
431
  Icon = "{section1_icon}",
238
432
  IconType = IconType.Lucide,
239
- Route = "/{contextCode}/{appCode}/{moduleCode}/{section1Code}",
433
+ Route = ToKebabCase($"/{contextCode}/{appCode}/{moduleCode}/{section1Code}"),
240
434
  DisplayOrder = {section1_sort},
241
435
  IsActive = true
242
436
  }
@@ -325,7 +519,7 @@ public static IEnumerable<NavigationResourceSeedEntry> GetResourceEntries(Guid s
325
519
  Code = "{resource1Code}",
326
520
  Label = "{resource1_label_en}",
327
521
  EntityType = "{resource1_entity}",
328
- Route = "/{contextCode}/{appCode}/{moduleCode}/{section1Code}/{resource1Code}",
522
+ Route = ToKebabCase($"/{contextCode}/{appCode}/{moduleCode}/{section1Code}/{resource1Code}"),
329
523
  DisplayOrder = 1
330
524
  }
331
525
  // Repeat for each resource in this section...
@@ -559,8 +753,8 @@ namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;
559
753
  /// </summary>
560
754
  public static class ApplicationRolesSeedData
561
755
  {
562
- // Application ID from NavigationApplicationSeedData
563
- private static readonly Guid ApplicationId = {ApplicationGuid};
756
+ // Application ID from NavigationApplicationSeedData (deterministic GUID)
757
+ public static readonly Guid ApplicationId = NavigationApplicationSeedData.ApplicationId;
564
758
 
565
759
  public static readonly Guid AdminRoleId = GenerateRoleGuid("admin");
566
760
  public static readonly Guid ManagerRoleId = GenerateRoleGuid("manager");
@@ -738,30 +932,30 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
738
932
 
739
933
  public async Task SeedNavigationAsync(ICoreDbContext context, CancellationToken ct)
740
934
  {
741
- // --- Application ---
935
+ // --- Application (from NavigationApplicationSeedData) ---
936
+ var appEntry = NavigationApplicationSeedData.GetApplicationEntry(Guid.Empty); // contextId resolved below
742
937
  var exists = await context.NavigationApplications
743
- .AnyAsync(a => a.Code == "{appCode}", ct);
938
+ .AnyAsync(a => a.Code == appEntry.Code, ct);
744
939
  if (exists) return;
745
940
 
746
941
  var parentContext = await context.NavigationContexts
747
942
  .FirstAsync(c => c.Code == "{contextCode}", ct);
748
943
 
944
+ // Re-get entry with resolved contextId
945
+ appEntry = NavigationApplicationSeedData.GetApplicationEntry(parentContext.Id);
749
946
  var app = NavigationApplication.Create(
750
- parentContext.Id, "{appCode}", "{appLabel_en}",
751
- "{appDesc_en}", "{appIcon}", IconType.Lucide,
752
- "/{contextCode}/{appCode}", 1);
947
+ appEntry.ContextId, appEntry.Code, appEntry.Label,
948
+ appEntry.Description, appEntry.Icon, appEntry.IconType,
949
+ appEntry.Route, appEntry.DisplayOrder);
753
950
  context.NavigationApplications.Add(app);
754
951
  await ((DbContext)context).SaveChangesAsync(ct);
755
952
 
756
- // --- Application translations (4 languages) ---
757
- var appTranslations = new[]
953
+ // --- Application translations (4 languages, from NavigationApplicationSeedData) ---
954
+ foreach (var t in NavigationApplicationSeedData.GetTranslationEntries())
758
955
  {
759
- NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "fr", "{appLabel_fr}", "{appDesc_fr}"),
760
- NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "en", "{appLabel_en}", "{appDesc_en}"),
761
- NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "it", "{appLabel_it}", "{appDesc_it}"),
762
- NavigationTranslation.Create(NavigationEntityType.Application, app.Id, "de", "{appLabel_de}", "{appDesc_de}")
763
- };
764
- foreach (var t in appTranslations) context.NavigationTranslations.Add(t);
956
+ context.NavigationTranslations.Add(
957
+ NavigationTranslation.Create(t.EntityType, t.EntityId, t.LanguageCode, t.Label, t.Description));
958
+ }
765
959
  await ((DbContext)context).SaveChangesAsync(ct);
766
960
 
767
961
  // --- Modules ---
@@ -926,7 +1120,8 @@ When processing multiple modules in the same ralph-loop run:
926
1120
 
927
1121
  ### Module 1 (first): Creates everything from scratch
928
1122
 
929
- 1. `ApplicationRolesSeedData.cs` (application-level, once per app)
1123
+ 0. `NavigationApplicationSeedData.cs` (**application-level**, once per app — FIRST FILE, provides ApplicationId)
1124
+ 1. `ApplicationRolesSeedData.cs` (application-level, once per app — references NavigationApplicationSeedData.ApplicationId)
930
1125
  2. `{Module1}NavigationSeedData.cs` (module + sections + resources + all translations)
931
1126
  3. `{Module1}PermissionsSeedData.cs`
932
1127
  4. `{Module1}RolesSeedData.cs`
@@ -935,6 +1130,7 @@ When processing multiple modules in the same ralph-loop run:
935
1130
 
936
1131
  ### Module 2+ (subsequent): Append to existing provider
937
1132
 
1133
+ 0. `NavigationApplicationSeedData.cs` (already exists — skip)
938
1134
  1. `ApplicationRolesSeedData.cs` (already exists — skip)
939
1135
  2. `{Module2}NavigationSeedData.cs` (new file — include sections + resources if defined in feature.json)
940
1136
  3. `{Module2}PermissionsSeedData.cs` (new file)
@@ -942,7 +1138,7 @@ When processing multiple modules in the same ralph-loop run:
942
1138
  5. `{AppPascalName}SeedDataProvider.cs` (**modify** — add using, add entries in Navigation/Permissions/RolePermissions methods, **including section/resource seeding for the new module**)
943
1139
  6. DI registration (already exists — skip)
944
1140
 
945
- **Detection:** Check if `{AppPascalName}SeedDataProvider.cs` exists. If yes, READ it and ADD the new module's entries to the appropriate methods (Navigation, Permissions, RolePermissions). Do NOT modify SeedRolesAsync().
1141
+ **Detection:** Check if `{AppPascalName}SeedDataProvider.cs` exists. If yes, READ it and ADD the new module's entries to the appropriate methods (Navigation, Permissions, RolePermissions). Do NOT modify SeedRolesAsync() or the Application creation in SeedNavigationAsync().
946
1142
 
947
1143
  ### Section/Resource Conditionality
948
1144
 
@@ -960,6 +1156,14 @@ Sections and resources are **optional per module**. When processing a module's f
960
1156
 
961
1157
  Before marking the task as completed, verify ALL:
962
1158
 
1159
+ **Application-Level (FIRST — before modules):**
1160
+ - [ ] `NavigationApplicationSeedData.cs` created (once per application, at `Infrastructure/Persistence/Seeding/Data/`)
1161
+ - [ ] Application GUID is deterministic (SHA256 of `"navigation-application-{contextCode}.{appCode}"`)
1162
+ - [ ] Application translations created (4 languages: fr, en, it, de, EntityType = Application)
1163
+ - [ ] `IClientSeedDataProvider.SeedNavigationAsync()` uses `NavigationApplicationSeedData` (NO hardcoded `{appLabel_en}` / `{appIcon}` placeholders)
1164
+ - [ ] `ApplicationRolesSeedData.ApplicationId` references `NavigationApplicationSeedData.ApplicationId` (NO `{ApplicationGuid}` placeholder)
1165
+
1166
+ **Module-Level:**
963
1167
  - [ ] Deterministic GUIDs (NEVER `Guid.NewGuid()`) — SHA256 of path
964
1168
  - [ ] 4 languages for each navigation entity (fr, en, it, de)
965
1169
  - [ ] `ApplicationRolesSeedData.cs` created (once per application)
@@ -969,7 +1173,7 @@ Before marking the task as completed, verify ALL:
969
1173
  - [ ] MCP `generate_permissions` called (or fallback used)
970
1174
  - [ ] Role-permission mappings assigned (Admin, Manager, Contributor, Viewer)
971
1175
  - [ ] `IClientSeedDataProvider` generated with 4 methods (Navigation, Roles, Permissions, RolePermissions)
972
- - [ ] Execution order: Navigation (modules → sections → resources) → Roles → Permissions → RolePermissions
1176
+ - [ ] Execution order: Navigation (application → modules → sections → resources) → Roles → Permissions → RolePermissions
973
1177
  - [ ] Each Seed method is idempotent (checks existence before inserting)
974
1178
  - [ ] Factory methods used throughout (NEVER `new Entity()`)
975
1179
  - [ ] `SaveChangesAsync` called per group (Navigation → Roles → Permissions → RolePermissions)
@@ -981,6 +1185,13 @@ Before marking the task as completed, verify ALL:
981
1185
  - [ ] NO `Enum.Parse<PermissionAction>` usage anywhere in seeding code (use typed enum directly)
982
1186
  - [ ] ALL PermissionAction values are from the valid enum: Access, Read, Create, Update, Delete, Export, Import, Approve, Reject, Assign, Execute
983
1187
 
1188
+ **Seed Data Integrity (BLOCKING — run AFTER `dotnet build`):**
1189
+ - [ ] Application startup test: `dotnet run --urls http://localhost:0 --environment Development` exits without seed data exceptions
1190
+ - [ ] Verify `nav_Applications` has entry for `{appCode}` (query or startup log)
1191
+ - [ ] Verify `nav_Modules` has entries for each module (count matches feature.json modules)
1192
+ - [ ] Verify `auth_Roles` has 4 application-scoped roles (admin, manager, contributor, viewer)
1193
+ - [ ] Verify `auth_Permissions` has entries for each module (wildcard + CRUD)
1194
+
984
1195
  **If ANY check fails, the task status = 'failed'.**
985
1196
 
986
1197
  ---
@@ -186,7 +186,9 @@ Initialize `.ralph/progress.txt`:
186
186
  > This check prevents the "no frontend generated" failure mode.
187
187
 
188
188
  ```javascript
189
- const REQUIRED_CATEGORIES = ['domain', 'infrastructure', 'application', 'api', 'frontend', 'test'];
189
+ // CRITICAL: seedData MUST be in the required list — without it, nav_Applications/auth_Roles/auth_Permissions
190
+ // are not seeded, and frontend/test tasks execute against an empty database.
191
+ const REQUIRED_CATEGORIES = ['domain', 'infrastructure', 'application', 'api', 'seedData', 'frontend', 'test'];
190
192
  const presentCategories = new Set(prd.tasks.map(t => t.category));
191
193
  const missingCategories = REQUIRED_CATEGORIES.filter(c => !presentCategories.has(c));
192
194
 
@@ -199,19 +201,38 @@ if (missingCategories.length > 0) {
199
201
  return isNaN(num) ? 0 : num;
200
202
  }));
201
203
  const prefix = prd.tasks[0]?.id?.replace(/[0-9]+$/, '') || 'GUARD-';
202
- const lastBackendTask = prd.tasks.filter(t => t.category === 'api').pop()?.id || prd.tasks[prd.tasks.length - 1]?.id;
204
+ const lastApiTask = prd.tasks.filter(t => t.category === 'api').pop()?.id || prd.tasks[prd.tasks.length - 1]?.id;
205
+ const lastSeedDataTask = prd.tasks.filter(t => t.category === 'seedData').pop()?.id;
206
+
207
+ // DEPENDENCY CHAIN: domain → infrastructure → application → api → seedData → frontend → test
208
+ // Frontend MUST depend on seedData (not just API) — otherwise navigation/permissions are empty
209
+ const frontendDep = lastSeedDataTask || lastApiTask;
203
210
 
204
211
  for (const cat of missingCategories) {
205
212
  maxIdNum++;
206
213
  const taskId = `${prefix}${String(maxIdNum).padStart(3, '0')}`;
214
+
215
+ // Determine dependencies based on category order
216
+ let deps;
217
+ if (cat === 'seedData') {
218
+ deps = [lastApiTask]; // seedData depends on API (infrastructure must exist first)
219
+ } else if (cat === 'frontend') {
220
+ deps = [frontendDep]; // frontend depends on seedData (navigation must be seeded)
221
+ } else if (cat === 'test') {
222
+ deps = [frontendDep]; // test depends on seedData too
223
+ } else {
224
+ deps = [];
225
+ }
226
+
207
227
  const guardrailTask = {
208
228
  id: taskId,
209
229
  description: `[GUARDRAIL] Generate ${cat} layer for ${prd.project?.module || 'module'}`,
210
230
  status: 'pending',
211
231
  category: cat,
212
- dependencies: cat === 'test' ? [lastBackendTask] : (cat === 'frontend' ? [lastBackendTask] : []),
232
+ dependencies: deps,
213
233
  acceptance_criteria: [
214
- cat === 'frontend' ? 'React pages created via MCP scaffold_api_client + scaffold_routes, wired to App.tsx' :
234
+ cat === 'seedData' ? 'NavigationApplicationSeedData + NavigationModuleSeedData + PermissionsSeedData + ApplicationRolesSeedData created, IClientSeedDataProvider wired, startup test passes' :
235
+ cat === 'frontend' ? 'React pages created via MCP scaffold_api_client + scaffold_routes, wired to App.tsx, navigation seed data verified in DB' :
215
236
  cat === 'test' ? 'Unit + Integration test projects created, scaffold_tests MCP called, dotnet test passes' :
216
237
  `${cat} layer fully implemented per category-rules.md`
217
238
  ],
@@ -220,6 +241,17 @@ if (missingCategories.length > 0) {
220
241
  };
221
242
  prd.tasks.push(guardrailTask);
222
243
  console.log(` → Injected guardrail: [${taskId}] ${cat}`);
244
+
245
+ // Update frontendDep if we just injected seedData (frontend guardrail should depend on it)
246
+ if (cat === 'seedData') {
247
+ // Re-resolve for subsequent iterations
248
+ const updatedFrontendDep = taskId;
249
+ // Patch any previously-added frontend guardrail to depend on this seedData task
250
+ const frontendGuard = prd.tasks.find(t => t.category === 'frontend' && t.description.includes('[GUARDRAIL]'));
251
+ if (frontendGuard && !frontendGuard.dependencies.includes(taskId)) {
252
+ frontendGuard.dependencies = [taskId];
253
+ }
254
+ }
223
255
  }
224
256
 
225
257
  writeJSON('.ralph/prd.json', prd);
@@ -83,6 +83,87 @@ dotnet build --no-restore
83
83
 
84
84
  MUST pass before proceeding to application/api/test/frontend. Fix if fails.
85
85
 
86
+ **POST-CHECK: Route kebab-case validation (BLOCKING)**
87
+
88
+ After generating NavigationSeedData files, verify routes follow kebab-case convention:
89
+
90
+ ```bash
91
+ # Search for routes with uppercase in NavigationSeedData files
92
+ UPPERCASE_ROUTES=$(grep -E 'Route = "?/[^"]*[A-Z]' Infrastructure/Persistence/Seeding/Data/*/NavigationSeedData.cs 2>/dev/null)
93
+
94
+ if [ -n "$UPPERCASE_ROUTES" ]; then
95
+ echo "❌ ERROR: Routes must be kebab-case lowercase. Found PascalCase in NavigationSeedData routes:"
96
+ echo "$UPPERCASE_ROUTES"
97
+ echo ""
98
+ echo "Expected format: /business/human-resources/projects"
99
+ echo "Fix: Use ToKebabCase() helper in route generation (see core-seed-data.md)"
100
+ exit 1
101
+ fi
102
+ ```
103
+
104
+ **Why this matters:**
105
+ - Backend seed data defines menu navigation routes
106
+ - Frontend React Router expects kebab-case URLs
107
+ - Mismatch causes 404 on menu click
108
+ - Convention: `HumanResources` (C# code) → `human-resources` (web URL)
109
+
110
+ **Fix:** If check fails, routes were generated without `ToKebabCase()` transformation. Regenerate NavigationSeedData with the helper.
111
+
112
+ **POST-CHECK: Core Seed Data Integrity (BLOCKING)**
113
+
114
+ After generating ALL seed data files (NavigationApplicationSeedData, NavigationModuleSeedData, PermissionsSeedData, RolesSeedData, ApplicationRolesSeedData, IClientSeedDataProvider), verify the complete chain:
115
+
116
+ ```bash
117
+ # 1. NavigationApplicationSeedData.cs exists
118
+ APP_SEED=$(find . -path "*/Seeding/Data/NavigationApplicationSeedData.cs" 2>/dev/null | head -1)
119
+ if [ -z "$APP_SEED" ]; then
120
+ echo "❌ BLOCKING: NavigationApplicationSeedData.cs NOT FOUND"
121
+ echo "Without this, nav_Applications is empty → navigation menu invisible"
122
+ echo "Fix: Generate from core-seed-data.md section 1b using seedDataCore.navigationApplications"
123
+ exit 1
124
+ fi
125
+
126
+ # 2. ApplicationRolesSeedData.cs references NavigationApplicationSeedData.ApplicationId (no placeholder)
127
+ if grep -q '{ApplicationGuid}' Infrastructure/Persistence/Seeding/Data/ApplicationRolesSeedData.cs 2>/dev/null; then
128
+ echo "❌ BLOCKING: ApplicationRolesSeedData still has {ApplicationGuid} placeholder"
129
+ echo "Fix: Replace with NavigationApplicationSeedData.ApplicationId"
130
+ exit 1
131
+ fi
132
+
133
+ # 3. IClientSeedDataProvider has no hardcoded app placeholders
134
+ PROVIDER=$(find . -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
135
+ if [ -n "$PROVIDER" ]; then
136
+ if grep -qE '\{appLabel_|appDesc_|appIcon\}' "$PROVIDER" 2>/dev/null; then
137
+ echo "❌ BLOCKING: SeedDataProvider has hardcoded {appLabel_xx}/{appIcon} placeholders"
138
+ echo "Fix: Use NavigationApplicationSeedData.GetApplicationEntry() and GetTranslationEntries()"
139
+ exit 1
140
+ fi
141
+ fi
142
+
143
+ # 4. Quick startup test — verify seed data doesn't crash at runtime
144
+ echo "Running startup test to verify seed data..."
145
+ dotnet run --project "$API_PROJECT" --urls "http://localhost:0" -- --environment Development &
146
+ RUN_PID=$!
147
+ sleep 8
148
+
149
+ if ! kill -0 $RUN_PID 2>/dev/null; then
150
+ echo "❌ BLOCKING: Application crashed during startup (seed data likely failed)"
151
+ echo "Check logs for: navigation, role, or permission seed data errors"
152
+ wait $RUN_PID 2>/dev/null
153
+ exit 1
154
+ else
155
+ echo "✓ Startup test passed — seed data executed without crash"
156
+ kill $RUN_PID 2>/dev/null
157
+ wait $RUN_PID 2>/dev/null
158
+ fi
159
+ ```
160
+
161
+ **Why this matters:**
162
+ - If `nav_Applications` is empty → navigation menu shows nothing → user can't access modules
163
+ - If `auth_Roles` is empty → role-permission mappings fail silently → RBAC broken
164
+ - If `auth_Permissions` is empty → all authorization checks reject → 403 on every endpoint
165
+ - These are **foundational** — without them, ALL subsequent features (frontend, API, tests) are useless
166
+
86
167
  ### 6. Validate with MCP
87
168
 
88
169
  ```