@atlashub/smartstack-cli 3.14.0 → 3.15.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 (33) hide show
  1. package/dist/index.js +26 -28
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +626 -141
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/agents/efcore/migration.md +15 -0
  7. package/templates/skills/apex/steps/step-04-validate.md +64 -5
  8. package/templates/skills/application/references/frontend-verification.md +20 -0
  9. package/templates/skills/application/steps/step-04-backend.md +17 -1
  10. package/templates/skills/application/steps/step-05-frontend.md +49 -23
  11. package/templates/skills/application/templates-seed.md +14 -4
  12. package/templates/skills/business-analyse/html/ba-interactive.html +165 -0
  13. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +2 -0
  14. package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +85 -0
  15. package/templates/skills/business-analyse/html/src/styles/05-modules.css +65 -0
  16. package/templates/skills/business-analyse/html/src/template.html +13 -0
  17. package/templates/skills/business-analyse/schemas/application-schema.json +5 -0
  18. package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +1 -0
  19. package/templates/skills/business-analyse/steps/step-01-cadrage.md +90 -0
  20. package/templates/skills/business-analyse/steps/step-02-decomposition.md +4 -0
  21. package/templates/skills/business-analyse/steps/step-03a1-setup.md +39 -0
  22. package/templates/skills/efcore/steps/shared/step-00-init.md +55 -0
  23. package/templates/skills/ralph-loop/SKILL.md +1 -0
  24. package/templates/skills/ralph-loop/references/category-rules.md +131 -27
  25. package/templates/skills/ralph-loop/references/compact-loop.md +61 -3
  26. package/templates/skills/ralph-loop/references/core-seed-data.md +251 -5
  27. package/templates/skills/ralph-loop/references/error-classification.md +143 -0
  28. package/templates/skills/ralph-loop/steps/step-05-report.md +54 -0
  29. package/templates/skills/review-code/references/smartstack-conventions.md +16 -0
  30. package/templates/skills/validate-feature/SKILL.md +11 -1
  31. package/templates/skills/validate-feature/steps/step-00-dependencies.md +121 -0
  32. package/templates/skills/validate-feature/steps/step-04-api-smoke.md +61 -13
  33. package/templates/skills/validate-feature/steps/step-05-db-validation.md +250 -0
@@ -43,6 +43,17 @@ Rules:
43
43
  **MCP:** `suggest_migration` → get migration name
44
44
 
45
45
  Execution sequence:
46
+ 0. Ensure `dotnet ef` is on PATH (platform-aware):
47
+ ```bash
48
+ if ! dotnet ef --version &>/dev/null; then
49
+ for TOOLS_DIR in "$USERPROFILE/.dotnet/tools" "$HOME/.dotnet/tools" "$LOCALAPPDATA/Microsoft/dotnet/tools"; do
50
+ [ -n "$TOOLS_DIR" ] && [ -d "$TOOLS_DIR" ] && export PATH="$TOOLS_DIR:$PATH"
51
+ done
52
+ dotnet ef --version &>/dev/null || { echo "ERROR: dotnet-ef not found. Install: dotnet tool install --global dotnet-ef"; exit 1; }
53
+ fi
54
+ ```
55
+ > **Why:** On Windows (Git Bash), `$USERPROFILE/.dotnet/tools` is the correct path.
56
+ > NEVER use `$HOME/.dotnet/tools` alone — on WSL, `$HOME` resolves to `/home/{user}` where .NET SDK is not installed.
46
57
  1. Call `mcp__smartstack__suggest_migration` → get standardized name
47
58
  2. `dotnet ef migrations add {Name} --project src/{Infra}.csproj --startup-project src/{Api}.csproj -o Persistence/Migrations`
48
59
  3. `dotnet ef database update --project src/{Infra}.csproj --startup-project src/{Api}.csproj`
@@ -110,6 +121,11 @@ dotnet build --no-restore
110
121
  **BLOCKING:** Build MUST pass before proceeding to application/api/test/frontend.
111
122
  If build fails, fix infrastructure code first.
112
123
 
124
+ > **NOTE:** If build passes but you suspect assembly issues (e.g., new packages added to Infrastructure
125
+ > that may not be in the meta-package), also run a quick startup check:
126
+ > `dotnet run --project {ApiProject} --urls "http://localhost:5098" &` and verify the process survives 5 seconds.
127
+ > See `references/error-classification.md` for error classification if it crashes.
128
+
113
129
  ---
114
130
 
115
131
  ## Application
@@ -186,11 +202,12 @@ Rules:
186
202
 
187
203
  **Execution sequence (IN ORDER):**
188
204
  1. `mcp__smartstack__scaffold_api_client` → API client + types + React Query hook
189
- 2. `mcp__smartstack__scaffold_routes` → routes inside correct Layout wrapper
190
- 3. Create pages using SmartStack components
191
- 4. Create preferences hook: `use{Module}Preferences.ts`
192
- 5. Generate i18n (4 languages: fr, en, it, de)
193
- 6. `npm run typecheck` MUST pass (BLOCKING)
205
+ 2. `mcp__smartstack__scaffold_routes` (with `outputFormat: "clientRoutes"`) route registry + route fragments
206
+ 3. **Wire routes to App.tsx** → insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
207
+ 4. Create pages using SmartStack components
208
+ 5. Create preferences hook: `use{Module}Preferences.ts`
209
+ 6. Generate i18n (4 languages: fr, en, it, de)
210
+ 7. `npm run typecheck` MUST pass (BLOCKING)
194
211
 
195
212
  **Components:**
196
213
  - Lists: `SmartTable` + `SmartFilter` (NOT HTML `<table>`)
@@ -229,6 +246,8 @@ import axios from 'axios' → use @/services/api/apiClient
229
246
  <Route path="/business/app/mod" /> → MUST be nested inside Layout
230
247
  Only fr/en translations → MUST have 4 languages
231
248
  src/pages/{Module}/ → MUST be src/pages/{Context}/{App}/{Module}/
249
+ Routes generated but NOT added to App.tsx → MUST wire routes to App.tsx after scaffold_routes
250
+ Routes in standard block only → MUST also add to /t/:slug/ tenant block
232
251
  '00000000-0000-0000-0000-000000000000' → use dynamic ID from auth context or route params
233
252
  const api = axios.create(...) → use @/services/api/apiClient (single instance)
234
253
  useXxx with raw axios inside hooks → hooks MUST use the shared apiClient from @/services/api
@@ -252,38 +271,63 @@ useXxx with raw axios inside hooks → hooks MUST use the shared apiCl
252
271
 
253
272
  **Execution sequence:**
254
273
 
255
- 1. **Ensure test project exists:**
274
+ 1. **Ensure test projects exist:**
256
275
  ```bash
257
- TEST_PROJECT="tests/${PROJECT_NAME}.Tests.Unit"
258
- if [ ! -d "$TEST_PROJECT" ]; then
259
- dotnet new xunit -n "${PROJECT_NAME}.Tests.Unit" -o "$TEST_PROJECT"
260
- dotnet add "$TEST_PROJECT" package Moq
261
- dotnet add "$TEST_PROJECT" package FluentAssertions
262
- for proj in src/*/*.csproj; do dotnet add "$TEST_PROJECT" reference "$proj"; done
263
- dotnet sln add "$TEST_PROJECT/${PROJECT_NAME}.Tests.Unit.csproj"
276
+ # Unit test project
277
+ UNIT_TEST_PROJECT="tests/${PROJECT_NAME}.Tests.Unit"
278
+ if [ ! -d "$UNIT_TEST_PROJECT" ]; then
279
+ dotnet new xunit -n "${PROJECT_NAME}.Tests.Unit" -o "$UNIT_TEST_PROJECT"
280
+ dotnet add "$UNIT_TEST_PROJECT" package Moq
281
+ dotnet add "$UNIT_TEST_PROJECT" package FluentAssertions
282
+ for proj in src/*/*.csproj; do dotnet add "$UNIT_TEST_PROJECT" reference "$proj"; done
283
+ dotnet sln add "$UNIT_TEST_PROJECT/${PROJECT_NAME}.Tests.Unit.csproj"
284
+ fi
285
+
286
+ # Integration test project (SQL Server LocalDB)
287
+ INT_TEST_PROJECT="tests/${PROJECT_NAME}.Tests.Integration"
288
+ if [ ! -d "$INT_TEST_PROJECT" ]; then
289
+ dotnet new xunit -n "${PROJECT_NAME}.Tests.Integration" -o "$INT_TEST_PROJECT"
290
+ dotnet add "$INT_TEST_PROJECT" package FluentAssertions
291
+ dotnet add "$INT_TEST_PROJECT" package Microsoft.AspNetCore.Mvc.Testing
292
+ dotnet add "$INT_TEST_PROJECT" package Microsoft.EntityFrameworkCore.SqlServer
293
+ dotnet add "$INT_TEST_PROJECT" package Microsoft.Data.SqlClient
294
+ dotnet add "$INT_TEST_PROJECT" package Respawn
295
+ for proj in src/*/*.csproj; do dotnet add "$INT_TEST_PROJECT" reference "$proj"; done
296
+ dotnet sln add "$INT_TEST_PROJECT/${PROJECT_NAME}.Tests.Integration.csproj"
264
297
  fi
265
298
  ```
266
299
 
267
- 2. **Generate tests via MCP** (NOT manually):
268
- - Domain: `scaffold_tests` with target_layer="domain"
269
- - Service: `scaffold_tests` with target_layer="application"
270
- - Controller: `scaffold_tests` with target_layer="api", test_type="integration"
271
- - Security: `scaffold_tests` with test_type="security"
300
+ 2. **Generate test infrastructure** (FIRST, before entity tests):
301
+ - `scaffold_tests` with target="infrastructure" → generates DatabaseFixture, DatabaseCollection, SmartStackTestFactory, TestAuthHandler, TestTenantService, IntegrationTestBase, TestDataSeeder
302
+ - These use **SQL Server LocalDB** (not SQLite) → real migrations, real LINQ→SQL
272
303
 
273
- 3. **Run tests (BLOCKING):**
304
+ 3. **Generate entity tests via MCP** (NOT manually):
305
+ - Domain: `scaffold_tests` with target="entity"
306
+ - Service: `scaffold_tests` with target="service"
307
+ - Controller: `scaffold_tests` with target="controller", testTypes=["integration"]
308
+ - Repository: `scaffold_tests` with target="repository", testTypes=["integration"]
309
+ - Security: `scaffold_tests` with target="controller", testTypes=["security"]
310
+
311
+ 4. **Run tests (BLOCKING):**
274
312
  ```bash
275
- dotnet build "$TEST_PROJECT" --no-restore
276
- dotnet test "$TEST_PROJECT" --no-build --verbosity normal
313
+ dotnet build --no-restore
314
+ dotnet test --no-build --verbosity normal
277
315
  ```
316
+ Integration tests run against **real SQL Server LocalDB** via DatabaseFixture:
317
+ - Migrations are applied automatically (validates migration chain)
318
+ - Queries execute real T-SQL (validates LINQ→SQL translation)
319
+ - Multi-tenant isolation is enforced via global query filters on SQL Server
320
+ - Respawn resets data between tests (~50ms)
278
321
 
279
- 4. **Fix loop:** If tests fail → analyze → fix code (NOT tests) → rebuild → retest → loop until 100% pass
322
+ 5. **Fix loop:** If tests fail → analyze → fix code (NOT tests) → rebuild → retest → loop until 100% pass
280
323
 
281
- 5. **Coverage check:** `analyze_test_coverage` → must be >= 80%
324
+ 6. **Coverage check:** `analyze_test_coverage` → must be >= 80%
282
325
 
283
326
  **Completion criteria (ALL required):**
284
- - Test project exists
285
- - Tests generated via MCP
286
- - `dotnet test` exits 0 (all pass)
327
+ - Unit test project exists
328
+ - Integration test project exists with SQL Server LocalDB infrastructure
329
+ - Tests generated via MCP (scaffold_tests)
330
+ - `dotnet test` exits 0 (all pass — including integration tests on SQL Server)
287
331
  - Coverage >= 80%
288
332
  - No `[Fact(Skip = "...")]`
289
333
 
@@ -294,12 +338,72 @@ useXxx with raw axios inside hooks → hooks MUST use the shared apiCl
294
338
  **Execution sequence:**
295
339
 
296
340
  1. `dotnet clean && dotnet restore && dotnet build` → MUST pass
341
+
342
+ 1a. **DB migration validation (BLOCKING — if infrastructure tasks exist):**
343
+ ```bash
344
+ INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)
345
+ API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
346
+ if [ -n "$INFRA_PROJECT" ] && [ -n "$API_PROJECT" ]; then
347
+ # Check no pending model changes
348
+ dotnet ef migrations has-pending-model-changes \
349
+ --project "$INFRA_PROJECT" --startup-project "$API_PROJECT"
350
+ # Apply migrations on fresh SQL Server LocalDB
351
+ DB_NAME="SmartStack_Validation_$(date +%s)"
352
+ CONN_STRING="Server=(localdb)\\MSSQLLocalDB;Database=$DB_NAME;Integrated Security=true;TrustServerCertificate=true;Connect Timeout=120;"
353
+ dotnet ef database update --connection "$CONN_STRING" \
354
+ --project "$INFRA_PROJECT" --startup-project "$API_PROJECT"
355
+ # Cleanup
356
+ sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "IF DB_ID('$DB_NAME') IS NOT NULL BEGIN ALTER DATABASE [$DB_NAME] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [$DB_NAME]; END" 2>/dev/null
357
+ fi
358
+ ```
359
+ If FAIL → migration is broken → fix before continuing
360
+
361
+ 1b. **Runtime assembly validation (BLOCKING):**
362
+ ```bash
363
+ API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
364
+ if [ -n "$API_PROJECT" ]; then
365
+ dotnet run --project "$API_PROJECT" --urls "http://localhost:5098" > /tmp/ralph-validation-startup.log 2>&1 &
366
+ VAL_PID=$!
367
+
368
+ # Wait up to 10 seconds
369
+ STARTED=false
370
+ for i in $(seq 1 10); do
371
+ if ! kill -0 $VAL_PID 2>/dev/null; then
372
+ # Process crashed
373
+ CRASH_LOG=$(cat /tmp/ralph-validation-startup.log 2>/dev/null)
374
+ echo "VALIDATION FAILED: API startup crash"
375
+ echo "$CRASH_LOG"
376
+ break
377
+ fi
378
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5098/health 2>/dev/null)
379
+ if [ "$HTTP_CODE" != "000" ]; then
380
+ STARTED=true
381
+ break
382
+ fi
383
+ sleep 1
384
+ done
385
+
386
+ kill $VAL_PID 2>/dev/null
387
+ wait $VAL_PID 2>/dev/null
388
+ rm -f /tmp/ralph-validation-startup.log
389
+
390
+ if [ "$STARTED" != "true" ]; then
391
+ # Classify error per references/error-classification.md
392
+ # Provide actionable fix command
393
+ # DO NOT proceed to tests — fix the startup issue first
394
+ echo "VALIDATION FAILED: Runtime assembly error detected"
395
+ fi
396
+ fi
397
+ ```
398
+
297
399
  2. `dotnet test` (full suite) → MUST pass
298
400
  3. `mcp__smartstack__validate_conventions` → 0 errors
299
401
  4. Generate validation report in progress.txt
300
402
 
301
403
  **Completion criteria:**
302
404
  - Build exit code 0
303
- - Test exit code 0
405
+ - DB migrations apply on SQL Server LocalDB (no pending model changes)
406
+ - Runtime startup check passes (no assembly errors)
407
+ - Test exit code 0 (unit tests + integration tests on real SQL Server)
304
408
  - MCP validation 0 errors
305
409
  - Production ready = true
@@ -70,7 +70,13 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
70
70
  2. **BUILD verification (BLOCKING per task):**
71
71
  - Backend (domain/infra/app/api): `dotnet build --no-restore` → MUST exit 0
72
72
  - Frontend: `npm run typecheck` → MUST exit 0
73
- 3. If build fails → read FULL error output → fix source code → rebuild → loop until pass
73
+ 3. If build fails → **CLASSIFY ERROR FIRST** (read `references/error-classification.md`):
74
+ - **Category A/B (assembly/package):** Run `dotnet add package {Name}` → `dotnet restore` → rebuild. Do NOT edit source code.
75
+ - **Category C (DI):** Edit DI registration file only → rebuild.
76
+ - **Category D (migration):** Run migration commands → rebuild.
77
+ - **Category E (config):** Edit config file → rebuild.
78
+ - **Category F (code):** Fix source code → rebuild → loop until pass.
79
+ - **If 2+ rebuild attempts fail on same error and Category F was chosen:** Re-classify — likely wrong category.
74
80
  4. **ARTIFACT verification (BLOCKING per task):**
75
81
  - `files_created` MUST NOT be empty for code-generating tasks
76
82
  - Category-specific checks (see below)
@@ -97,7 +103,7 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
97
103
  |----------|--------|
98
104
  | `infrastructure` with `_migrationMeta` | Migration sequence: `suggest_migration` MCP → `dotnet ef migrations add` → `dotnet ef database update` → `dotnet build` |
99
105
  | `infrastructure` with seed data keywords | **MANDATORY:** Read `references/core-seed-data.md` → implement templates → `dotnet build` |
100
- | `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` → create pages → `npm run typecheck && npm run lint` |
106
+ | `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) **wire to App.tsx (standard + tenant blocks)** → create pages → `npm run typecheck && npm run lint` |
101
107
  | `test` | **Create tests:** `scaffold_tests` MCP → **Run:** `dotnet test --verbosity normal` → **Fix loop:** if fail → fix SOURCE CODE → rebuild → retest → repeat until 100% pass |
102
108
  | `validation` | `dotnet clean && dotnet restore && dotnet build` → `dotnet test` (full) → `validate_conventions` MCP |
103
109
 
@@ -116,7 +122,27 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
116
122
  ```
117
123
  If FAIL → fix source code (NOT tests) → rebuild → retest → loop until 100% pass
118
124
 
119
- 2bis. **DI completeness (if application tasks in batch):**
125
+ 2bis. **Runtime assembly check (if api/validation tasks in batch):**
126
+ ```bash
127
+ API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
128
+ if [ -n "$API_PROJECT" ]; then
129
+ dotnet run --project "$API_PROJECT" --urls "http://localhost:5098" > /tmp/ralph-startup-check.log 2>&1 &
130
+ CHECK_PID=$!
131
+ sleep 5
132
+ if ! kill -0 $CHECK_PID 2>/dev/null; then
133
+ CRASH_OUTPUT=$(cat /tmp/ralph-startup-check.log 2>/dev/null)
134
+ echo "RUNTIME ASSEMBLY ERROR DETECTED"
135
+ # Classify per references/error-classification.md
136
+ # If Category A: dotnet add package → rebuild → DO NOT count as iteration failure
137
+ fi
138
+ kill $CHECK_PID 2>/dev/null
139
+ wait $CHECK_PID 2>/dev/null
140
+ rm -f /tmp/ralph-startup-check.log
141
+ fi
142
+ ```
143
+ If FAIL → classify error per `references/error-classification.md` → apply fix → rebuild → verify again
144
+
145
+ 2ter. **DI completeness (if application tasks in batch):**
120
146
  Verify `Application/DependencyInjection.cs` registers all validators and services:
121
147
  - Count validators in `Validators/` folder
122
148
  - Verify `AddValidatorsFromAssemblyContaining` is present
@@ -138,6 +164,38 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
138
164
  4. **MCP validation:**
139
165
  `mcp__smartstack__validate_conventions` ONCE for the whole batch
140
166
 
167
+ 5. **DB validation (if infrastructure/migration tasks in batch):**
168
+ ```bash
169
+ # Quick check: pending model changes after batch?
170
+ INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)
171
+ API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
172
+ if [ -n "$INFRA_PROJECT" ] && [ -n "$API_PROJECT" ]; then
173
+ dotnet ef migrations has-pending-model-changes \
174
+ --project "$INFRA_PROJECT" \
175
+ --startup-project "$API_PROJECT"
176
+ if [ $? -ne 0 ]; then
177
+ echo "MIGRATION MISSING: Model has uncommitted changes"
178
+ # Create fix task for missing migration
179
+ fi
180
+
181
+ # If batch included migration tasks: verify migration applies on real SQL Server
182
+ DB_NAME="SmartStack_Ralph_Check_${iteration}"
183
+ CONN_STRING="Server=(localdb)\\MSSQLLocalDB;Database=$DB_NAME;Integrated Security=true;TrustServerCertificate=true;Connect Timeout=120;"
184
+ dotnet ef database update \
185
+ --connection "$CONN_STRING" \
186
+ --project "$INFRA_PROJECT" \
187
+ --startup-project "$API_PROJECT"
188
+ if [ $? -ne 0 ]; then
189
+ echo "MIGRATION BROKEN: Cannot apply migrations on SQL Server"
190
+ # Fix migration → rebuild → DO NOT commit until this passes
191
+ fi
192
+
193
+ # Cleanup temp database
194
+ sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "IF DB_ID('$DB_NAME') IS NOT NULL BEGIN ALTER DATABASE [$DB_NAME] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [$DB_NAME]; END" 2>/dev/null
195
+ fi
196
+ ```
197
+ If FAIL → migration is broken → fix → rebuild → DO NOT commit
198
+
141
199
  **Error resolution cycle:**
142
200
  1. Read the FULL error output (not just first line)
143
201
  2. Identify root cause: file path + line number
@@ -19,6 +19,8 @@ const coreSeedData = meta.coreSeedData || {};
19
19
 
20
20
  // Navigation hierarchy
21
21
  const navModules = coreSeedData.navigationModules || coreSeedData.navigation || [];
22
+ const navSections = coreSeedData.navigationSections || [];
23
+ const navResources = coreSeedData.navigationResources || [];
22
24
  const permissions = coreSeedData.permissions || [];
23
25
  const rolePerms = coreSeedData.rolePermissions || [];
24
26
 
@@ -44,6 +46,8 @@ if (!navRoute) {
44
46
  | `appCode` | `humanresources` | `_seedDataMeta.appCode` |
45
47
  | `moduleCode` | `projects` | `task.module` |
46
48
  | `navModules[]` | `[{code, label, icon, route, translations}]` | `coreSeedData.navigationModules` |
49
+ | `navSections[]` | `[{code, label, icon, route, parentCode, permission, sort}]` | `coreSeedData.navigationSections` |
50
+ | `navResources[]` | `[{code, type, entity, parentCode, permission}]` | `coreSeedData.navigationResources` |
47
51
  | `permissions[]` | `[{path, action, description}]` | `coreSeedData.permissions` |
48
52
  | `rolePerms[]` | `[{role, permissions[]}]` | `coreSeedData.rolePermissions` |
49
53
 
@@ -187,6 +191,198 @@ public class NavigationTranslationSeedEntry
187
191
 
188
192
  ---
189
193
 
194
+ ## 2b. Navigation Sections & Resources (in same NavigationSeedData.cs)
195
+
196
+ > **CONDITIONAL:** Only generate if `seedDataCore.navigationSections[]` exists and is non-empty in feature.json.
197
+ > Sections and resources are added as additional methods in the **same** `{ModulePascal}NavigationSeedData.cs` file.
198
+
199
+ ### GUID Generation Rules
200
+
201
+ ```csharp
202
+ // Section GUID: deterministic from navRoute + section code
203
+ public static readonly Guid {SectionPascal}SectionId =
204
+ GenerateDeterministicGuid("navigation-section-{navRoute}.{sectionCode}");
205
+ // Example: GenerateDeterministicGuid("navigation-section-business.ressourceshumaines.employees.list")
206
+
207
+ // Resource GUID: deterministic from navRoute + section code + resource code
208
+ public static readonly Guid {ResourcePascal}ResourceId =
209
+ GenerateDeterministicGuid("navigation-resource-{navRoute}.{sectionCode}.{resourceCode}");
210
+ // Example: GenerateDeterministicGuid("navigation-resource-business.ressourceshumaines.employees.list.employees-grid")
211
+ ```
212
+
213
+ ### Section Methods (add to {ModulePascal}NavigationSeedData.cs)
214
+
215
+ ```csharp
216
+ // --- Add AFTER GetTranslationEntries() in {ModulePascal}NavigationSeedData.cs ---
217
+
218
+ // Deterministic GUIDs for sections
219
+ public static readonly Guid {Section1Pascal}SectionId =
220
+ GenerateDeterministicGuid("navigation-section-{navRoute}.{section1Code}");
221
+ // Repeat for each section...
222
+
223
+ /// <summary>
224
+ /// Returns navigation section entries for seeding into core.nav_Sections.
225
+ /// </summary>
226
+ public static IEnumerable<NavigationSectionSeedEntry> GetSectionEntries(Guid moduleId)
227
+ {
228
+ return new[]
229
+ {
230
+ new NavigationSectionSeedEntry
231
+ {
232
+ Id = {Section1Pascal}SectionId,
233
+ ModuleId = moduleId,
234
+ Code = "{section1Code}",
235
+ Label = "{section1_label_en}",
236
+ Description = "{section1_desc_en}",
237
+ Icon = "{section1_icon}",
238
+ IconType = IconType.Lucide,
239
+ Route = "/{contextCode}/{appCode}/{moduleCode}/{section1Code}",
240
+ DisplayOrder = {section1_sort},
241
+ IsActive = true
242
+ }
243
+ // Repeat for each section...
244
+ };
245
+ }
246
+
247
+ /// <summary>
248
+ /// Returns 4-language translations for sections.
249
+ /// </summary>
250
+ public static IEnumerable<NavigationTranslationSeedEntry> GetSectionTranslationEntries()
251
+ {
252
+ var entries = new List<NavigationTranslationSeedEntry>();
253
+
254
+ // Section: {section1Code}
255
+ var sec1Id = {Section1Pascal}SectionId;
256
+ entries.AddRange(new[]
257
+ {
258
+ new NavigationTranslationSeedEntry
259
+ {
260
+ EntityType = NavigationEntityType.Section,
261
+ EntityId = sec1Id,
262
+ LanguageCode = "fr",
263
+ Label = "{section1_label_fr}",
264
+ Description = "{section1_desc_fr}"
265
+ },
266
+ new NavigationTranslationSeedEntry
267
+ {
268
+ EntityType = NavigationEntityType.Section,
269
+ EntityId = sec1Id,
270
+ LanguageCode = "en",
271
+ Label = "{section1_label_en}",
272
+ Description = "{section1_desc_en}"
273
+ },
274
+ new NavigationTranslationSeedEntry
275
+ {
276
+ EntityType = NavigationEntityType.Section,
277
+ EntityId = sec1Id,
278
+ LanguageCode = "it",
279
+ Label = "{section1_label_it}",
280
+ Description = "{section1_desc_it}"
281
+ },
282
+ new NavigationTranslationSeedEntry
283
+ {
284
+ EntityType = NavigationEntityType.Section,
285
+ EntityId = sec1Id,
286
+ LanguageCode = "de",
287
+ Label = "{section1_label_de}",
288
+ Description = "{section1_desc_de}"
289
+ }
290
+ });
291
+ // Repeat for each section...
292
+
293
+ return entries;
294
+ }
295
+ ```
296
+
297
+ ### Resource Methods (add to {ModulePascal}NavigationSeedData.cs)
298
+
299
+ > **CONDITIONAL:** Only generate if `seedDataCore.navigationResources[]` exists and is non-empty.
300
+
301
+ ```csharp
302
+ // --- Add AFTER GetSectionTranslationEntries() ---
303
+
304
+ // Deterministic GUIDs for resources
305
+ public static readonly Guid {Resource1Pascal}ResourceId =
306
+ GenerateDeterministicGuid("navigation-resource-{navRoute}.{parentSectionCode}.{resource1Code}");
307
+ // Repeat for each resource...
308
+
309
+ /// <summary>
310
+ /// Returns navigation resource entries for a given section.
311
+ /// </summary>
312
+ public static IEnumerable<NavigationResourceSeedEntry> GetResourceEntries(Guid sectionId)
313
+ {
314
+ var entries = new List<NavigationResourceSeedEntry>();
315
+
316
+ // Resources for section: {section1Code}
317
+ if (sectionId == {Section1Pascal}SectionId)
318
+ {
319
+ entries.AddRange(new[]
320
+ {
321
+ new NavigationResourceSeedEntry
322
+ {
323
+ Id = {Resource1Pascal}ResourceId,
324
+ SectionId = sectionId,
325
+ Code = "{resource1Code}",
326
+ Label = "{resource1_label_en}",
327
+ EntityType = "{resource1_entity}",
328
+ Route = "/{contextCode}/{appCode}/{moduleCode}/{section1Code}/{resource1Code}",
329
+ DisplayOrder = 1
330
+ }
331
+ // Repeat for each resource in this section...
332
+ });
333
+ }
334
+ // Repeat if-block for each section with resources...
335
+
336
+ return entries;
337
+ }
338
+ ```
339
+
340
+ ### Additional DTO Classes (add to bottom of file)
341
+
342
+ ```csharp
343
+ /// <summary>Seed entry DTO for navigation section.</summary>
344
+ public class NavigationSectionSeedEntry
345
+ {
346
+ public Guid Id { get; init; }
347
+ public Guid ModuleId { get; init; }
348
+ public string Code { get; init; } = null!;
349
+ public string Label { get; init; } = null!;
350
+ public string Description { get; init; } = null!;
351
+ public string Icon { get; init; } = null!;
352
+ public IconType IconType { get; init; }
353
+ public string Route { get; init; } = null!;
354
+ public int DisplayOrder { get; init; }
355
+ public bool IsActive { get; init; }
356
+ }
357
+
358
+ /// <summary>Seed entry DTO for navigation resource.</summary>
359
+ public class NavigationResourceSeedEntry
360
+ {
361
+ public Guid Id { get; init; }
362
+ public Guid SectionId { get; init; }
363
+ public string Code { get; init; } = null!;
364
+ public string Label { get; init; } = null!;
365
+ public string? EntityType { get; init; }
366
+ public string? Route { get; init; }
367
+ public int DisplayOrder { get; init; }
368
+ }
369
+ ```
370
+
371
+ ### Placeholder Values Source
372
+
373
+ | Placeholder | Source in feature.json |
374
+ |-------------|----------------------|
375
+ | `{sectionCode}` | `seedDataCore.navigationSections[].code` |
376
+ | `{section_label_xx}` | `specification.navigation.entries[]` where `level == "section"` → `labels.xx` |
377
+ | `{section_icon}` | `seedDataCore.navigationSections[].icon` |
378
+ | `{section_sort}` | `seedDataCore.navigationSections[].sort` |
379
+ | `{section_route}` | `seedDataCore.navigationSections[].route` |
380
+ | `{resourceCode}` | `seedDataCore.navigationResources[].code` |
381
+ | `{resource_entity}` | `seedDataCore.navigationResources[].entity` |
382
+ | `{parentSectionCode}` | `seedDataCore.navigationResources[].parentCode` |
383
+
384
+ ---
385
+
190
386
  ## 3. PermissionsSeedData.cs — MCP-First
191
387
 
192
388
  ### Step A: Call MCP (PRIMARY)
@@ -542,6 +738,43 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
542
738
  }
543
739
  // Repeat for each module...
544
740
  await ((DbContext)context).SaveChangesAsync(ct);
741
+
742
+ // --- Sections (if seedDataCore.navigationSections exists) ---
743
+ // Module 1 sections
744
+ foreach (var secEntry in {Module1Pascal}NavigationSeedData.GetSectionEntries(mod1.Id))
745
+ {
746
+ var sec = NavigationSection.Create(
747
+ secEntry.ModuleId, secEntry.Code, secEntry.Label,
748
+ secEntry.Description, secEntry.Icon, secEntry.IconType,
749
+ secEntry.Route, secEntry.DisplayOrder);
750
+ context.NavigationSections.Add(sec);
751
+ }
752
+ // Repeat for each module that has sections...
753
+ await ((DbContext)context).SaveChangesAsync(ct);
754
+
755
+ // --- Section translations ---
756
+ foreach (var t in {Module1Pascal}NavigationSeedData.GetSectionTranslationEntries())
757
+ {
758
+ context.NavigationTranslations.Add(
759
+ NavigationTranslation.Create(t.EntityType, t.EntityId, t.LanguageCode, t.Label, t.Description));
760
+ }
761
+ // Repeat for each module that has sections...
762
+ await ((DbContext)context).SaveChangesAsync(ct);
763
+
764
+ // --- Resources (if seedDataCore.navigationResources exists) ---
765
+ // Module 1 resources (resolved per section)
766
+ foreach (var secEntry in {Module1Pascal}NavigationSeedData.GetSectionEntries(mod1.Id))
767
+ {
768
+ foreach (var resEntry in {Module1Pascal}NavigationSeedData.GetResourceEntries(secEntry.Id))
769
+ {
770
+ var res = NavigationResource.Create(
771
+ resEntry.SectionId, resEntry.Code, resEntry.Label,
772
+ resEntry.EntityType, resEntry.Route, resEntry.DisplayOrder);
773
+ context.NavigationResources.Add(res);
774
+ }
775
+ }
776
+ // Repeat for each module that has resources...
777
+ await ((DbContext)context).SaveChangesAsync(ct);
545
778
  }
546
779
 
547
780
  public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
@@ -648,23 +881,33 @@ When processing multiple modules in the same ralph-loop run:
648
881
  ### Module 1 (first): Creates everything from scratch
649
882
 
650
883
  1. `ApplicationRolesSeedData.cs` (application-level, once per app)
651
- 2. `{Module1}NavigationSeedData.cs`
884
+ 2. `{Module1}NavigationSeedData.cs` (module + sections + resources + all translations)
652
885
  3. `{Module1}PermissionsSeedData.cs`
653
886
  4. `{Module1}RolesSeedData.cs`
654
- 5. `{AppPascalName}SeedDataProvider.cs` (new, with 4 methods)
887
+ 5. `{AppPascalName}SeedDataProvider.cs` (new, with 4 methods — including section/resource seeding)
655
888
  6. DI registration (new)
656
889
 
657
890
  ### Module 2+ (subsequent): Append to existing provider
658
891
 
659
892
  1. `ApplicationRolesSeedData.cs` (already exists — skip)
660
- 2. `{Module2}NavigationSeedData.cs` (new file)
893
+ 2. `{Module2}NavigationSeedData.cs` (new file — include sections + resources if defined in feature.json)
661
894
  3. `{Module2}PermissionsSeedData.cs` (new file)
662
895
  4. `{Module2}RolesSeedData.cs` (new file)
663
- 5. `{AppPascalName}SeedDataProvider.cs` (**modify** — add using, add entries in Navigation/Permissions/RolePermissions methods)
896
+ 5. `{AppPascalName}SeedDataProvider.cs` (**modify** — add using, add entries in Navigation/Permissions/RolePermissions methods, **including section/resource seeding for the new module**)
664
897
  6. DI registration (already exists — skip)
665
898
 
666
899
  **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().
667
900
 
901
+ ### Section/Resource Conditionality
902
+
903
+ Sections and resources are **optional per module**. When processing a module's feature.json:
904
+
905
+ | Condition | Action |
906
+ |-----------|--------|
907
+ | `seedDataCore.navigationSections` absent or empty | Skip `GetSectionEntries()` / `GetSectionTranslationEntries()` / section seeding in provider |
908
+ | `seedDataCore.navigationResources` absent or empty | Skip `GetResourceEntries()` / resource seeding in provider |
909
+ | Both present | Generate all section + resource methods and provider code |
910
+
668
911
  ---
669
912
 
670
913
  ## 8. Verification Checklist (BLOCKING)
@@ -680,11 +923,14 @@ Before marking the task as completed, verify ALL:
680
923
  - [ ] MCP `generate_permissions` called (or fallback used)
681
924
  - [ ] Role-permission mappings assigned (Admin, Manager, Contributor, Viewer)
682
925
  - [ ] `IClientSeedDataProvider` generated with 4 methods (Navigation, Roles, Permissions, RolePermissions)
683
- - [ ] Execution order: Navigation → Roles → Permissions → RolePermissions
926
+ - [ ] Execution order: Navigation (modules sections → resources) → Roles → Permissions → RolePermissions
684
927
  - [ ] Each Seed method is idempotent (checks existence before inserting)
685
928
  - [ ] Factory methods used throughout (NEVER `new Entity()`)
686
929
  - [ ] `SaveChangesAsync` called per group (Navigation → Roles → Permissions → RolePermissions)
687
930
  - [ ] DI registration added: `services.AddScoped<IClientSeedDataProvider, ...>()`
931
+ - [ ] NavigationSections seeded (if `seedDataCore.navigationSections` present in feature.json)
932
+ - [ ] NavigationResources seeded (if `seedDataCore.navigationResources` present in feature.json)
933
+ - [ ] Section/Resource translations created (4 languages each, EntityType = Section/Resource)
688
934
  - [ ] `dotnet build` passes after generation
689
935
 
690
936
  **If ANY check fails, the task status = 'failed'.**