@atlashub/smartstack-cli 3.34.0 → 3.35.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/.documentation/init.html +409 -0
  2. package/dist/index.js +32 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/mcp-entry.mjs +7 -24
  5. package/dist/mcp-entry.mjs.map +1 -1
  6. package/package.json +1 -2
  7. package/templates/mcp-scaffolding/controller.cs.hbs +5 -1
  8. package/templates/skills/apex/SKILL.md +3 -3
  9. package/templates/skills/apex/references/post-checks.md +225 -0
  10. package/templates/skills/apex/references/smartstack-api.md +29 -1
  11. package/templates/skills/apex/references/smartstack-frontend.md +27 -0
  12. package/templates/skills/apex/references/smartstack-layers.md +18 -2
  13. package/templates/skills/apex/steps/step-00-init.md +73 -0
  14. package/templates/skills/apex/steps/step-01-analyze.md +21 -0
  15. package/templates/skills/apex/steps/step-03-execute.md +72 -5
  16. package/templates/skills/apex/steps/step-04-examine.md +7 -1
  17. package/templates/skills/business-analyse/SKILL.md +4 -3
  18. package/templates/skills/business-analyse/_shared.md +9 -0
  19. package/templates/skills/business-analyse/schemas/application-schema.json +13 -0
  20. package/templates/skills/business-analyse/steps/step-00-init.md +190 -34
  21. package/templates/skills/business-analyse/steps/step-01-cadrage.md +129 -10
  22. package/templates/skills/business-analyse/steps/step-01b-applications.md +184 -13
  23. package/templates/skills/business-analyse/steps/step-03c-compile.md +14 -2
  24. package/templates/skills/business-analyse/steps/step-03d-validate.md +5 -1
  25. package/templates/skills/ralph-loop/SKILL.md +5 -0
  26. package/templates/skills/ralph-loop/references/category-rules.md +29 -0
  27. package/templates/skills/ralph-loop/references/compact-loop.md +85 -2
  28. package/templates/skills/ralph-loop/references/section-splitting.md +439 -0
  29. package/templates/skills/ralph-loop/steps/step-01-task.md +27 -0
  30. package/templates/skills/ralph-loop/steps/step-02-execute.md +45 -1
  31. package/templates/skills/ralph-loop/steps/step-05-report.md +19 -0
  32. package/scripts/health-check.sh +0 -168
  33. package/scripts/postinstall.js +0 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "3.34.0",
3
+ "version": "3.35.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -21,7 +21,6 @@
21
21
  "dist",
22
22
  "templates",
23
23
  "config",
24
- "scripts",
25
24
  ".documentation"
26
25
  ],
27
26
  "engines": {
@@ -6,6 +6,9 @@ using Microsoft.AspNetCore.Authorization;
6
6
  using Microsoft.AspNetCore.Mvc;
7
7
  using Microsoft.Extensions.Logging;
8
8
  using SmartStack.Api.Authorization;
9
+ {{#if navRoute}}
10
+ using SmartStack.Api.Routing;
11
+ {{/if}}
9
12
  using SmartStack.Application.Common.Models;
10
13
 
11
14
  namespace {{namespace}}.Controllers;
@@ -14,9 +17,10 @@ namespace {{namespace}}.Controllers;
14
17
  /// API controller for {{name}} operations
15
18
  /// </summary>
16
19
  [ApiController]
17
- [Route("api/[controller]")]
18
20
  {{#if navRoute}}
19
21
  [NavRoute("{{navRoute}}")]
22
+ {{else}}
23
+ [Route("api/[controller]")]
20
24
  {{/if}}
21
25
  [Authorize]
22
26
  [Produces("application/json")]
@@ -86,7 +86,7 @@ Execute incremental SmartStack development using the APEX methodology. This skil
86
86
  | 01 | `steps/step-01-analyze.md` | Opus | Explore existing code (Agent Teams or direct) |
87
87
  | 02 | `steps/step-02-plan.md` | Opus | Layer-by-layer plan with skill/MCP mapping |
88
88
  | 03 | `steps/step-03-execute.md` | Opus | Orchestrate execution via skills and MCP |
89
- | 04 | `steps/step-04-examine.md` | Opus | eXamine: MCP validation, build, 21 POST-CHECKs, acceptance criteria |
89
+ | 04 | `steps/step-04-examine.md` | Opus | eXamine: MCP validation, build, 50 POST-CHECKs, acceptance criteria |
90
90
  | 05 | `steps/step-05-deep-review.md` | Opus | Deep Review: adversarial code review (if -x) |
91
91
  | 06 | `steps/step-06-resolve.md` | Opus | Fix BLOCKING findings (if any) |
92
92
  | 07 | `steps/step-07-tests.md` | Opus | Scaffold tests via MCP |
@@ -98,11 +98,11 @@ Execute incremental SmartStack development using the APEX methodology. This skil
98
98
 
99
99
  | Phase | Step | Obligatory | Description |
100
100
  |-------|------|------------|-------------|
101
- | *Init* | 00 | Yes | Setup, hierarchy, challenge the need (skipped in `-d` delegate mode) |
101
+ | *Init* | 00 | Yes | Setup, hierarchy, challenge the need, **scope guard** (skipped in `-d` delegate mode) |
102
102
  | **A** — Analyze | 01 | Yes | Explore existing code |
103
103
  | **P** — Plan | 02 | Yes | File-by-file plan with skill/MCP mapping |
104
104
  | **E** — Execute | 03 | Yes | Orchestrate creation via skills and MCP |
105
- | **X** — eXamine | 04 | Yes | 30 POST-CHECKs, MCP validation, build, acceptance criteria |
105
+ | **X** — eXamine | 04 | Yes | 50 POST-CHECKs, MCP validation, build, acceptance criteria |
106
106
  | *Deep Review* | 05 (if -x) | No | Adversarial code review beyond automated checks |
107
107
  | *Resolve* | 06 (if BLOCKING) | No | Fix BLOCKING findings |
108
108
  | *Tests* | 07-08 | **Yes** | Scaffold and run tests |
@@ -1251,4 +1251,229 @@ if [ -n "$DETAIL_PAGES" ]; then
1251
1251
  fi
1252
1252
  ```
1253
1253
 
1254
+ ### POST-CHECK 44: Migration ModelSnapshot must contain ALL entities registered in DbContext (BLOCKING)
1255
+
1256
+ ```bash
1257
+ # Root cause (test-apex-007): 7 entities registered in DbContext but migration only covered 3.
1258
+ # Happens when migration is created ONCE in Layer 0 for the first batch, then additional entities
1259
+ # are added in subsequent iterations without re-running migration.
1260
+ SNAPSHOT=$(find src/ -name "*ModelSnapshot.cs" -path "*/Migrations/*" 2>/dev/null | head -1)
1261
+ DBCONTEXT=$(find src/ -name "*DbContext.cs" -path "*/Persistence/*" ! -name "*DesignTime*" 2>/dev/null | head -1)
1262
+ if [ -n "$SNAPSHOT" ] && [ -n "$DBCONTEXT" ]; then
1263
+ # Extract DbSet entity names from DbContext (DbSet<EntityName>)
1264
+ DBSET_ENTITIES=$(grep -oP 'DbSet<(\w+)>' "$DBCONTEXT" 2>/dev/null | grep -oP '<\K\w+(?=>)' | sort -u)
1265
+ FAIL=false
1266
+ for ENTITY in $DBSET_ENTITIES; do
1267
+ # Skip base SmartStack entities (handled by core migrations)
1268
+ if echo "$ENTITY" | grep -qP '^(Navigation|Tenant|User|Role|Permission|AuditLog|ApplicationTracking)'; then
1269
+ continue
1270
+ fi
1271
+ # Check if the entity appears in ModelSnapshot (builder.Entity<EntityName>)
1272
+ if ! grep -q "Entity<$ENTITY>" "$SNAPSHOT" 2>/dev/null; then
1273
+ echo "BLOCKING: Entity '$ENTITY' is registered as DbSet in $DBCONTEXT but MISSING from ModelSnapshot"
1274
+ echo " This means no migration was created for this entity — it will not exist in the database."
1275
+ echo " Fix: Run 'dotnet ef migrations add' to include all new entities"
1276
+ FAIL=true
1277
+ fi
1278
+ done
1279
+ if [ "$FAIL" = true ]; then
1280
+ echo ""
1281
+ echo " Root cause: Migration was likely created once for the first batch of entities,"
1282
+ echo " but additional entities were added later without regenerating the migration."
1283
+ echo " Fix: Create a new migration that covers ALL missing entities."
1284
+ exit 1
1285
+ fi
1286
+ fi
1287
+ ```
1288
+
1289
+ ### POST-CHECK 45: I18n namespace files must be registered in i18n config (BLOCKING)
1290
+
1291
+ ```bash
1292
+ # Root cause (test-apex-007): i18n JSON files existed in src/i18n/locales/ but were never
1293
+ # registered in the i18n config (config.ts or index.ts). Pages calling useTranslation(['module'])
1294
+ # got empty translations at runtime.
1295
+ I18N_CONFIG=$(find src/ web/ -path "*/i18n/config.ts" -o -path "*/i18n/index.ts" -o -path "*/i18n/i18n.ts" 2>/dev/null | grep -v node_modules | head -1)
1296
+ if [ -n "$I18N_CONFIG" ]; then
1297
+ # Find all module JSON files in the primary language (fr)
1298
+ FR_FILES=$(find src/ web/ -path "*/i18n/locales/fr/*.json" 2>/dev/null | grep -v node_modules | grep -v common.json | grep -v navigation.json)
1299
+ if [ -n "$FR_FILES" ]; then
1300
+ FAIL=false
1301
+ for JSON_FILE in $FR_FILES; do
1302
+ NS=$(basename "$JSON_FILE" .json)
1303
+ # Check if namespace is referenced in config (import or resource key)
1304
+ if ! grep -q "$NS" "$I18N_CONFIG" 2>/dev/null; then
1305
+ echo "BLOCKING: i18n namespace '$NS' (from $JSON_FILE) is not registered in $I18N_CONFIG"
1306
+ echo " Pages using useTranslation(['$NS']) will get empty translations at runtime"
1307
+ echo " Fix: Add '$NS' to the resources/ns configuration in $I18N_CONFIG"
1308
+ FAIL=true
1309
+ fi
1310
+ done
1311
+ if [ "$FAIL" = true ]; then
1312
+ exit 1
1313
+ fi
1314
+ fi
1315
+ fi
1316
+ ```
1317
+
1318
+ ### POST-CHECK 46: FluentValidation validators must be registered via DI (BLOCKING)
1319
+
1320
+ ```bash
1321
+ # Root cause (test-apex-007): Validators existed but were never registered in DI.
1322
+ # Without DI registration, [FromBody] DTOs are never validated — any data is accepted.
1323
+ VALIDATOR_FILES=$(find src/ -name "*Validator.cs" -path "*/Validators/*" 2>/dev/null | grep -v test | grep -v Test)
1324
+ if [ -n "$VALIDATOR_FILES" ]; then
1325
+ # Check DI registration file exists
1326
+ DI_FILE=$(find src/ -name "DependencyInjection.cs" -o -name "ServiceCollectionExtensions.cs" 2>/dev/null | grep -v test | head -1)
1327
+ if [ -z "$DI_FILE" ]; then
1328
+ echo "BLOCKING: Validators exist but no DependencyInjection.cs found for DI registration"
1329
+ exit 1
1330
+ fi
1331
+ # Check for AddValidatorsFromAssembly or individual validator registration
1332
+ HAS_ASSEMBLY_REG=$(grep -c "AddValidatorsFromAssembly\|AddValidatorsFromAssemblyContaining" "$DI_FILE" 2>/dev/null)
1333
+ if [ "$HAS_ASSEMBLY_REG" -eq 0 ]; then
1334
+ # Check individual registrations as fallback
1335
+ VALIDATOR_COUNT=$(echo "$VALIDATOR_FILES" | wc -l)
1336
+ REGISTERED_COUNT=0
1337
+ for VF in $VALIDATOR_FILES; do
1338
+ VN=$(basename "$VF" .cs)
1339
+ if grep -q "$VN" "$DI_FILE" 2>/dev/null; then
1340
+ REGISTERED_COUNT=$((REGISTERED_COUNT + 1))
1341
+ fi
1342
+ done
1343
+ if [ "$REGISTERED_COUNT" -eq 0 ]; then
1344
+ echo "BLOCKING: $VALIDATOR_COUNT validators exist but NONE are registered in DI ($DI_FILE)"
1345
+ echo " Fix: Add 'services.AddValidatorsFromAssemblyContaining<Create{Entity}DtoValidator>();' to $DI_FILE"
1346
+ echo " Or use 'services.AddValidatorsFromAssembly(typeof(Create{Entity}DtoValidator).Assembly);'"
1347
+ exit 1
1348
+ fi
1349
+ fi
1350
+ fi
1351
+ ```
1352
+
1353
+ ### POST-CHECK 47: Date/date properties in DTOs must use DateOnly, not string (BLOCKING)
1354
+
1355
+ ```bash
1356
+ # Root cause (test-apex-007): WorkLog DTO had Date property typed as string instead of DateOnly.
1357
+ # This causes: invalid date parsing, no date validation, inconsistent formats across clients.
1358
+ DTO_FILES=$(find src/ -name "*Dto.cs" -path "*/DTOs/*" 2>/dev/null)
1359
+ if [ -n "$DTO_FILES" ]; then
1360
+ FAIL=false
1361
+ for f in $DTO_FILES; do
1362
+ # Find string properties whose name contains "Date" (case-insensitive)
1363
+ BAD_DATES=$(grep -Pn 'string\??\s+\w*[Dd]ate\w*\s*[{;,]' "$f" 2>/dev/null | grep -vi "Updated\|Created\|format\|pattern\|string\|parse")
1364
+ if [ -n "$BAD_DATES" ]; then
1365
+ echo "BLOCKING: DTO has string type for date field — must use DateOnly: $f"
1366
+ echo "$BAD_DATES"
1367
+ echo " Fix: Change 'string Date' to 'DateOnly Date' (or 'DateOnly? Date' if nullable)"
1368
+ echo " DateOnly is the correct .NET type for date-only values (no time component)"
1369
+ FAIL=true
1370
+ fi
1371
+ done
1372
+ if [ "$FAIL" = true ]; then
1373
+ exit 1
1374
+ fi
1375
+ fi
1376
+ ```
1377
+
1378
+ ### POST-CHECK 48: NavRoute attribute values must use kebab-case (BLOCKING)
1379
+
1380
+ ```bash
1381
+ # Root cause (test-apex-007): Controllers had [NavRoute("business.humanresources.employees")]
1382
+ # instead of [NavRoute("business.human-resources.employees")]. This causes route mismatch with
1383
+ # seed data and permission codes, resulting in 404s at runtime.
1384
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
1385
+ if [ -n "$CTRL_FILES" ]; then
1386
+ FAIL=false
1387
+ for f in $CTRL_FILES; do
1388
+ NAVROUTE_VALS=$(grep -oP 'NavRoute\("([^"]+)"' "$f" 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"')
1389
+ for NR in $NAVROUTE_VALS; do
1390
+ # Check each segment for concatenated multi-word without hyphens
1391
+ SEGMENTS=$(echo "$NR" | tr '.' '\n')
1392
+ for SEG in $SEGMENTS; do
1393
+ # Detect segments that look like concatenated words (lowercase, 8+ chars, no hyphens)
1394
+ # Use a simpler heuristic: lowercase-only segment with known multi-word patterns
1395
+ if echo "$SEG" | grep -qP '^[a-z]{8,}$'; then
1396
+ # Additional check: does it contain a known multi-word pattern?
1397
+ if echo "$SEG" | grep -qP '(human|project|leave|client|support|email|time|work|resource)'; then
1398
+ echo "BLOCKING: NavRoute segment '$SEG' in $f appears to be concatenated multi-word without hyphens"
1399
+ echo " Full NavRoute: $NR"
1400
+ echo " Fix: Use kebab-case: e.g., 'humanresources' → 'human-resources', 'projectmanagement' → 'project-management'"
1401
+ FAIL=true
1402
+ fi
1403
+ fi
1404
+ done
1405
+ done
1406
+ done
1407
+ if [ "$FAIL" = true ]; then
1408
+ exit 1
1409
+ fi
1410
+ fi
1411
+ ```
1412
+
1413
+ ### POST-CHECK 49: Every module with entities must have a migration covering them (BLOCKING)
1414
+
1415
+ ```bash
1416
+ # Complementary to POST-CHECK 44 — checks from the entity side.
1417
+ # Finds entity .cs files in Domain/ and verifies they appear in at least one migration file.
1418
+ ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null | grep -v test)
1419
+ MIGRATION_DIR=$(find src/ -path "*/Migrations" -type d 2>/dev/null | head -1)
1420
+ if [ -n "$ENTITY_FILES" ] && [ -n "$MIGRATION_DIR" ]; then
1421
+ MIGRATION_FILES=$(find "$MIGRATION_DIR" -name "*.cs" ! -name "*ModelSnapshot*" ! -name "*DesignTime*" 2>/dev/null)
1422
+ if [ -z "$MIGRATION_FILES" ]; then
1423
+ echo "BLOCKING: Entity files exist in Domain/Entities but NO migration files found in $MIGRATION_DIR"
1424
+ exit 1
1425
+ fi
1426
+ FAIL=false
1427
+ for EF in $ENTITY_FILES; do
1428
+ ENTITY_NAME=$(basename "$EF" .cs)
1429
+ # Skip abstract base classes and interfaces
1430
+ if grep -qP '^\s*(public\s+)?(abstract|interface)\s' "$EF" 2>/dev/null; then continue; fi
1431
+ # Check if entity appears in any migration (CreateTable or AddColumn or entity reference)
1432
+ FOUND=$(grep -l "$ENTITY_NAME" $MIGRATION_FILES 2>/dev/null)
1433
+ if [ -z "$FOUND" ]; then
1434
+ echo "BLOCKING: Entity '$ENTITY_NAME' ($EF) not found in any migration file"
1435
+ echo " This entity will NOT have a database table."
1436
+ echo " Fix: Run 'dotnet ef migrations add' to create a migration covering this entity"
1437
+ FAIL=true
1438
+ fi
1439
+ done
1440
+ if [ "$FAIL" = true ]; then
1441
+ exit 1
1442
+ fi
1443
+ fi
1444
+ ```
1445
+
1446
+ ### POST-CHECK 50: Controllers must NOT have both [Route] and [NavRoute] attributes (BLOCKING)
1447
+
1448
+ ```bash
1449
+ # Root cause (test-apex-007): All 7 controllers had BOTH [Route("api/...")] and [NavRoute("...")].
1450
+ # In SmartStack, [NavRoute] resolves routes dynamically from Navigation entities at startup.
1451
+ # [Route] is standard ASP.NET Core static routing. When both exist:
1452
+ # - NavRoute middleware tries to resolve from DB → fails if seed data not applied → no route
1453
+ # - [Route] may or may not take over depending on middleware order
1454
+ # - Result: 404 on ALL endpoints
1455
+ # The MCP validate_conventions previously ENCOURAGED adding [Route] with [NavRoute] — this was a bug.
1456
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
1457
+ if [ -n "$CTRL_FILES" ]; then
1458
+ FAIL=false
1459
+ for f in $CTRL_FILES; do
1460
+ HAS_NAVROUTE=$(grep -c '\[NavRoute(' "$f" 2>/dev/null)
1461
+ HAS_ROUTE=$(grep -c '\[Route(' "$f" 2>/dev/null)
1462
+ if [ "$HAS_NAVROUTE" -gt 0 ] && [ "$HAS_ROUTE" -gt 0 ]; then
1463
+ NAVROUTE_VAL=$(grep -oP 'NavRoute\("([^"]+)"' "$f" 2>/dev/null | head -1)
1464
+ ROUTE_VAL=$(grep -oP 'Route\("([^"]+)"' "$f" 2>/dev/null | head -1)
1465
+ echo "BLOCKING: Controller has BOTH [Route] and [NavRoute] — remove [Route]: $f"
1466
+ echo " Found: [$ROUTE_VAL] + [$NAVROUTE_VAL]"
1467
+ echo " In SmartStack, [NavRoute] resolves routes dynamically from the database."
1468
+ echo " Having [Route] alongside it causes route conflicts and 404s."
1469
+ echo " Fix: Remove the [Route(...)] attribute, keep only [NavRoute(...)]"
1470
+ FAIL=true
1471
+ fi
1472
+ done
1473
+ if [ "$FAIL" = true ]; then
1474
+ exit 1
1475
+ fi
1476
+ fi
1477
+ ```
1478
+
1254
1479
  **If ANY POST-CHECK fails → fix in step-03, re-validate.**
@@ -491,6 +491,12 @@ public class {Name}Controller : ControllerBase
491
491
  }
492
492
  ```
493
493
 
494
+ **CRITICAL — Route attribute rules:**
495
+ - `[NavRoute]` is the ONLY route attribute needed — it resolves routes dynamically from Navigation entities at startup
496
+ - **FORBIDDEN:** `[Route("api/...")]` alongside `[NavRoute]` — causes route conflicts and 404s at runtime
497
+ - **FORBIDDEN:** `[Route("api/[controller]")]` — this is standard ASP.NET Core, NOT SmartStack
498
+ - If a controller has `[NavRoute]`, there must be NO `[Route]` attribute on the class
499
+
494
500
  **CRITICAL:** Use `[RequirePermission(Permissions.{Module}.{Action})]` on EVERY endpoint — NEVER `[Authorize]` alone (no RBAC enforcement).
495
501
 
496
502
  **CRITICAL — Permission paths use IDENTICAL segments to NavRoute codes (kebab-case):**
@@ -703,6 +709,26 @@ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
703
709
 
704
710
  ---
705
711
 
712
+ ## DTO Type Mapping (CRITICAL)
713
+
714
+ > **Use the correct .NET type for each property.** Incorrect types cause runtime parsing errors.
715
+
716
+ | Property Pattern | .NET Type | JSON Format | Example |
717
+ |-----------------|-----------|-------------|---------|
718
+ | `*Date`, `StartDate`, `EndDate`, `BirthDate` | `DateOnly` | `"2025-03-15"` | `public DateOnly Date { get; set; }` |
719
+ | `CreatedAt`, `UpdatedAt` | `DateTime` | `"2025-03-15T10:30:00Z"` | `public DateTime CreatedAt { get; set; }` |
720
+ | `*Time`, `StartTime` | `TimeOnly` | `"14:30:00"` | `public TimeOnly StartTime { get; set; }` |
721
+ | Duration, hours | `decimal` | `8.5` | `public decimal HoursWorked { get; set; }` |
722
+ | FK reference | `Guid` | `"uuid-string"` | `public Guid EmployeeId { get; set; }` |
723
+
724
+ **FORBIDDEN in DTOs:**
725
+ - `string Date` / `string StartDate` — use `DateOnly`
726
+ - `string Time` — use `TimeOnly`
727
+ - `DateTime BirthDate` — use `DateOnly` (no time component needed)
728
+ - `int` for hours/duration — use `decimal` for fractional values
729
+
730
+ ---
731
+
706
732
  ## Common Mistakes to Avoid
707
733
 
708
734
  | Mistake | Reality |
@@ -713,7 +739,7 @@ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
713
739
  | `e.IsDeleted` filter | Does NOT exist — no soft delete |
714
740
  | `SmartStack.Api.Core.Routing` | Wrong — use `SmartStack.Api.Routing` |
715
741
  | `SystemEntity` base class | Does NOT exist — use `BaseEntity` for all |
716
- | `[Route] + [NavRoute]` | Only `[NavRoute]` needed (resolves route from DB) |
742
+ | `[Route("api/...")] + [NavRoute]` | **FORBIDDEN** — causes 404s. Only `[NavRoute]` needed (resolves route from DB at startup). Remove ALL `[Route]` attributes when `[NavRoute]` is present. |
717
743
  | `SmartStack.Domain.Common.Interfaces` | Wrong — interfaces are in `SmartStack.Domain.Common` directly |
718
744
  | `[Authorize]` without `[RequirePermission]` | No RBAC enforcement — always use `[RequirePermission]` |
719
745
  | `tenantId: Guid.Empty` in services | OWASP A01 — always use validated `_currentTenant.TenantId` |
@@ -726,6 +752,8 @@ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
726
752
  | `business.humanresources.employees.read` in permissions | Permission segments MUST match NavRoute kebab-case: `business.human-resources.employees.read` |
727
753
  | `Permission.Create()` | Does NOT exist — use `CreateForModule()`, `CreateForSection()`, etc. |
728
754
  | `GetAllAsync()` without search param | ALL GetAll endpoints MUST support `?search=` for EntityLookup |
755
+ | `string Date` in DTO | Date-only fields MUST use `DateOnly`, NEVER `string` |
756
+ | `DateTime` for date-only | Use `DateOnly` when no time component needed |
729
757
  | FK field as plain text input | Frontend MUST use `EntityLookup` component for Guid FK fields |
730
758
  | `PagedResult<T>` / `PaginatedResultDto<T>` | FORBIDDEN — use `PaginatedResult<T>` only |
731
759
 
@@ -187,11 +187,38 @@ t('common:actions.save', 'Save')
187
187
  t('common:errors.network', 'Network error')
188
188
  ```
189
189
 
190
+ ### Namespace Registration (CRITICAL)
191
+
192
+ > **After creating i18n JSON files, you MUST register each namespace in the i18n config.**
193
+ > Root cause (test-apex-007): JSON files existed but namespaces were not registered → `useTranslation(['module'])` returned empty strings.
194
+
195
+ In the i18n config file (`src/i18n/config.ts` or `src/i18n/index.ts`), add each new namespace:
196
+
197
+ ```typescript
198
+ // Example: registering new module namespaces
199
+ import employees from './locales/fr/employees.json';
200
+ import projects from './locales/fr/projects.json';
201
+ import clients from './locales/fr/clients.json';
202
+
203
+ // In resources configuration:
204
+ resources: {
205
+ fr: { employees, projects, clients, common, navigation },
206
+ en: { employees: employeesEn, projects: projectsEn, clients: clientsEn, ... },
207
+ // ... it, de
208
+ }
209
+
210
+ // OR with ns array:
211
+ ns: ['common', 'navigation', 'employees', 'projects', 'clients'],
212
+ ```
213
+
214
+ POST-CHECK 45 validates this. Unregistered namespaces → BLOCKING.
215
+
190
216
  ### Rules
191
217
 
192
218
  - **ALWAYS** provide a fallback value as 2nd argument to `t()`
193
219
  - **ALWAYS** use namespace prefix: `t('namespace:key')`
194
220
  - **ALWAYS** generate 4 language files (fr, en, it, de) with identical key structures
221
+ - **ALWAYS** register new namespaces in i18n config file after creating JSON files
195
222
  - **NEVER** hardcode user-facing strings in JSX
196
223
  - **NEVER** use `t('key')` without namespace prefix
197
224
 
@@ -51,6 +51,11 @@
51
51
 
52
52
  **BLOCKING:** `dotnet build --no-restore` MUST pass after migration.
53
53
 
54
+ > **CRITICAL — Migration must cover ALL entities (POST-CHECK 44, 49):**
55
+ > Create/update migration AFTER ALL entities and EF configs are registered in DbContext.
56
+ > Verify with `dotnet ef migrations has-pending-model-changes` — must report NO pending changes.
57
+ > If entities are added incrementally across modules, create a NEW migration for each batch.
58
+
54
59
  ---
55
60
 
56
61
  ## Layer 1 — Application (parallel with API)
@@ -75,10 +80,13 @@
75
80
  - **ALL services MUST inject `ICurrentUserService`** for audit trails
76
81
  - **ALL services MUST inject `ILogger<T>`** for production diagnostics
77
82
  - CQRS with MediatR
78
- - FluentValidation for all commands
83
+ - FluentValidation for all commands — **MUST register validators via DI:**
84
+ `services.AddValidatorsFromAssemblyContaining<Create{Entity}DtoValidator>();`
85
+ Without DI registration, `[FromBody]` DTOs are never validated (POST-CHECK 46)
86
+ - **Date fields in DTOs MUST use `DateOnly`**, not `string` (POST-CHECK 47). See `smartstack-api.md` DTO Type Mapping.
79
87
  - DTOs separate from domain entities
80
88
  - Service interfaces in Application, implementations in Infrastructure
81
- - **FORBIDDEN:** `tenantId: Guid.Empty`, `TenantId!.Value`, queries without TenantId filter, `ICurrentUser` (does not exist — use `ICurrentUserService` + `ICurrentTenantService`)
89
+ - **FORBIDDEN:** `tenantId: Guid.Empty`, `TenantId!.Value`, queries without TenantId filter, `ICurrentUser` (does not exist — use `ICurrentUserService` + `ICurrentTenantService`), `string` type for date fields
82
90
 
83
91
  ---
84
92
 
@@ -101,8 +109,16 @@
101
109
 
102
110
  **Rules:**
103
111
  - `[RequirePermission(Permissions.{Module}.{Action})]` on EVERY endpoint
112
+ - **NavRoute values MUST use kebab-case for ALL multi-word segments:**
113
+ - `[NavRoute("business.human-resources.employees")]` (CORRECT)
114
+ - `[NavRoute("business.humanresources.employees")]` (WRONG — missing hyphens)
115
+ - `[NavRoute("business.project-management.projects")]` (CORRECT)
116
+ - `[NavRoute("business.projectmanagement.projects")]` (WRONG)
104
117
  - Permission paths MUST use kebab-case matching NavRoute codes (e.g., `business.human-resources.employees.read`)
105
118
  - FORBIDDEN: concatenated segments like `humanresources` — must be `human-resources`
119
+ - POST-CHECK 48 detects non-kebab-case NavRoute values. POST-CHECK 41 detects non-kebab-case permissions.
120
+ - **FORBIDDEN:** `[Route("api/...")]` alongside `[NavRoute]` — causes 404s (POST-CHECK 50)
121
+ - `[NavRoute]` is the ONLY route attribute needed — resolves routes from DB at startup
106
122
  - NEVER use `[Authorize]` without specific permission
107
123
  - Swagger XML documentation
108
124
  - Return DTOs, never domain entities
@@ -194,6 +194,79 @@ These values are propagated to:
194
194
 
195
195
  ---
196
196
 
197
+ ## 5d. Scope Complexity Guard (BLOCKING)
198
+
199
+ > **Root cause (test-apex-007):** `/apex` was invoked with 3 applications and 7 entities.
200
+ > The model's context window saturated, causing: incomplete migrations, lost conventions,
201
+ > missing pages, unregistered i18n, forgotten DI registrations.
202
+ >
203
+ > `/apex` is designed for **incremental** development (1 module at a time).
204
+ > Multi-module projects should use `/ralph-loop` which calls `/apex -d` per module.
205
+
206
+ ### Scope Detection
207
+
208
+ ```
209
+ entity_count = {entities}.length
210
+ section_count = {sections}.length
211
+ app_count = number of DISTINCT applications detected in {task_description}
212
+ (heuristics: "application X et application Y", "module X, module Y, module Z"
213
+ with different app names, "3 apps", etc.)
214
+
215
+ scope_score = entity_count + (section_count * 0.5) + (app_count * 3)
216
+ ```
217
+
218
+ ### Guard Rules
219
+
220
+ ```
221
+ IF delegate_mode (-d flag):
222
+ → SKIP guard (ralph-loop already handles scope per module)
223
+
224
+ ELSE IF app_count > 1:
225
+ → BLOCKING: "Multiple applications detected ({app_count} apps, {entity_count} entities).
226
+ /apex handles 1 module at a time. For multi-module projects, use:
227
+ - /ralph-loop (automated: reads PRD, generates all modules sequentially)
228
+ - Multiple /apex calls (manual: one /apex per module)
229
+
230
+ To override this guard, split your request:
231
+ /apex -e add HR employee management
232
+ /apex -e add CRM client management
233
+ /apex -e add Project management"
234
+
235
+ ELSE IF scope_score > 8:
236
+ → WARNING: "Large scope detected ({entity_count} entities, {section_count} sections).
237
+ /apex works best with ≤ 4 entities per invocation.
238
+ Consider splitting into smaller iterations.
239
+ Proceeding, but expect higher risk of omissions."
240
+
241
+ AskUserQuestion:
242
+ header: "Scope"
243
+ question: "Le périmètre est large ({entity_count} entités). Réduire le scope ou continuer ?"
244
+ options:
245
+ - label: "Continue anyway"
246
+ description: "Proceed with full scope (higher risk of omissions)"
247
+ - label: "Split into iterations"
248
+ description: "Focus on the first module/app, then run /apex again for the rest"
249
+ - label: "Use /ralph-loop"
250
+ description: "Switch to /ralph-loop for automated multi-module orchestration"
251
+
252
+ IF "Split" → ask user which module to focus on first, update {entities}/{sections}
253
+ IF "/ralph-loop" → display command to run, STOP execution
254
+
255
+ ELSE:
256
+ → PASS (scope is manageable)
257
+ ```
258
+
259
+ ### Recommended Scope per /apex Invocation
260
+
261
+ | Metric | Safe | Warning | Blocking |
262
+ |--------|------|---------|----------|
263
+ | Applications | 1 | 1 (large) | >1 |
264
+ | Entities | 1-4 | 5-6 | >6 without -d |
265
+ | Sections | 1-3 | 4-5 | >5 without -d |
266
+ | Sub-resources | 0-2 | 3-4 | >4 without -d |
267
+
268
+ ---
269
+
197
270
  ## 6. Determine Needs
198
271
 
199
272
  ```
@@ -211,6 +211,27 @@ Cross-reference with step-00 challenge responses:
211
211
 
212
212
  ---
213
213
 
214
+ ## 5b. Scope Re-Check (after exploration)
215
+
216
+ > **Re-validate scope after code exploration reveals the true entity count.**
217
+ > Step-00 guard uses the user's description (may undercount). Now we know the actual entities.
218
+
219
+ ```
220
+ IF NOT delegate_mode:
221
+ actual_entities = count of entities marked "create" in gap analysis
222
+ actual_sections = count of sections marked "create"
223
+
224
+ IF actual_entities > 6:
225
+ WARNING: "Code exploration reveals {actual_entities} entities to create.
226
+ This exceeds the recommended maximum (4) for a single /apex invocation.
227
+ Risk: incomplete migrations, lost conventions, missing pages.
228
+ Consider: split into {ceil(actual_entities/4)} iterations of ~4 entities each."
229
+
230
+ AskUserQuestion: (same options as step-00 section 5d)
231
+ ```
232
+
233
+ ---
234
+
214
235
  ## 6. Analysis Validation (User Checkpoint)
215
236
 
216
237
  > **Objective:** Present findings and validate scope with the user BEFORE planning.
@@ -44,16 +44,26 @@ For each entity:
44
44
 
45
45
  ### Migration (BLOCKING)
46
46
 
47
+ > **CRITICAL — Migration must cover ALL entities, not just the first batch.**
48
+ > Root cause (test-apex-007): Migration was created once for 3 entities, then 4 more entities
49
+ > were added later without re-running migration → 4 entities had no database tables.
50
+ > **RULE:** Create/update migration AFTER ALL entities and EF configs are registered in DbContext.
51
+ > If entities are added incrementally, create a NEW migration for each batch.
52
+
47
53
  ```
48
- 1. MCP suggest_migration get standardized name
49
- 2. dotnet ef migrations add {Name} --project src/{Infra}.csproj --startup-project src/{Api}.csproj -o Persistence/Migrations
50
- 3. Cleanup corrupted EF Core artifacts:
54
+ 1. Verify ALL entities have been added as DbSet in ExtensionsDbContext
55
+ 2. Verify ALL EF configurations are registered (ApplyConfigurationsFromAssembly or individual)
56
+ 3. MCP suggest_migration get standardized name
57
+ 4. dotnet ef migrations add {Name} --project src/{Infra}.csproj --startup-project src/{Api}.csproj -o Persistence/Migrations
58
+ 5. Cleanup corrupted EF Core artifacts:
51
59
  for d in src/*/bin?Debug; do [ -d "$d" ] && rm -rf "$d"; done
52
- 4. dotnet ef database update (if local DB)
53
- 5. dotnet build → MUST PASS
60
+ 6. dotnet ef database update (if local DB)
61
+ 7. dotnet build → MUST PASS
62
+ 8. Verify: dotnet ef migrations has-pending-model-changes → MUST report "No pending model changes"
54
63
  ```
55
64
 
56
65
  **BLOCKING:** If build fails after migration, fix EF configs before proceeding.
66
+ **BLOCKING:** If `has-pending-model-changes` reports pending changes, entities are missing from the migration — create a new migration.
57
67
 
58
68
  ### Post-Layer 0 Build Gate
59
69
 
@@ -67,10 +77,66 @@ dotnet build
67
77
 
68
78
  ## Layer 1 — Application + API + Seed Data
69
79
 
80
+ ### NavRoute and Permission Kebab-Case (CRITICAL)
81
+
82
+ > **ALL NavRoute segments and permission codes MUST use kebab-case for multi-word identifiers.**
83
+ > Root cause (test-apex-007): Controllers had `[NavRoute("business.humanresources.employees")]`
84
+ > instead of `[NavRoute("business.human-resources.employees")]`. This mismatched seed data routes
85
+ > and permission codes, causing 404s and permission denials at runtime.
86
+
87
+ **Rules:**
88
+ - NavRoute: `business.human-resources.employees` (NEVER `business.humanresources.employees`)
89
+ - Permissions: `business.human-resources.employees.read` (segments MATCH NavRoute exactly)
90
+ - Seed data codes: `human-resources` (NEVER `humanresources`)
91
+ - C# class names stay PascalCase (`HumanResourcesController`) — only route/permission strings use kebab-case
92
+ - POST-CHECKs 41 + 48 validate this. Fix BEFORE committing.
93
+
94
+ ### Controller Route Attribute (BLOCKING)
95
+
96
+ > **Controllers with `[NavRoute]` must NOT have `[Route]` attribute.**
97
+ > Root cause (test-apex-007): ALL 7 controllers had BOTH `[Route("api/...")]` AND `[NavRoute("...")]`.
98
+ > In SmartStack, `[NavRoute]` resolves routes dynamically from Navigation entities in the database at startup.
99
+ > Having `[Route]` alongside causes route conflicts → all endpoints return 404.
100
+
101
+ **Rules:**
102
+ - `[NavRoute("context.app.module")]` is the ONLY route attribute needed on controllers
103
+ - **FORBIDDEN:** `[Route("api/business/human-resources/employees")]` alongside `[NavRoute]`
104
+ - **FORBIDDEN:** `[Route("api/[controller]")]` alongside `[NavRoute]`
105
+ - If generating via MCP `scaffold_extension` with `navRoute` option → output is correct (NavRoute only)
106
+ - If generating via `/controller` skill → verify NO `[Route]` is added
107
+ - POST-CHECK 50 validates this. Fix BEFORE committing.
108
+
109
+ ### Validators DI Registration (CRITICAL)
110
+
111
+ > After creating validators, they MUST be registered in DI. Without registration, `[FromBody]` DTOs are never validated.
112
+
113
+ ```
114
+ In DependencyInjection.cs (or ServiceCollectionExtensions.cs):
115
+ services.AddValidatorsFromAssemblyContaining<Create{Entity}DtoValidator>();
116
+ ```
117
+
118
+ POST-CHECK 46 validates this. If validators exist but no DI registration → BLOCKING.
119
+
70
120
  ### If economy_mode: Sequential execution
71
121
 
72
122
  Execute each item from the plan sequentially using skills and MCP.
73
123
 
124
+ ### Date Fields — Use DateOnly (CRITICAL)
125
+
126
+ > **ALL date-only fields in DTOs MUST use `DateOnly`, NEVER `string`.**
127
+ > Root cause (test-apex-007): WorkLog DTO had `string Date` instead of `DateOnly Date`.
128
+ > This causes: no date validation, inconsistent date formats, parsing errors.
129
+
130
+ **Type mapping for DTOs:**
131
+ | Domain type | DTO type | Example |
132
+ |-------------|----------|---------|
133
+ | `DateTime` | `DateTime` | `CreatedAt`, `UpdatedAt` |
134
+ | Date-only field | `DateOnly` | `Date`, `StartDate`, `EndDate`, `BirthDate` |
135
+ | `string` for date | **FORBIDDEN** | Never use `string` for dates |
136
+ | `DateTime` for date-only | **Avoid** | Use `DateOnly` when no time component needed |
137
+
138
+ POST-CHECK 47 validates this. If a DTO has `string` type for a property named `*Date*` → BLOCKING.
139
+
74
140
  ### Code Generation (if entities have codePattern != "manual")
75
141
 
76
142
  For each entity with auto-generated code pattern (from feature.json or step-02 decisions):
@@ -114,6 +180,7 @@ Code stays in CreateDto, user provides it, validator has regex rule.
114
180
  - A dead link (navigate to a route with no page) is a BLOCKING issue (POST-CHECK 42)
115
181
  - Read `references/smartstack-frontend.md` for mandatory patterns (sections 3b + 8)
116
182
  - Generate i18n JSON files for all 4 languages (fr, en, it, de) — `src/i18n/locales/{lang}/{module}.json`
183
+ - **I18n REGISTRATION (CRITICAL):** After creating i18n JSON files, register EACH new namespace in the i18n config file (config.ts/index.ts/i18n.ts). Unregistered namespaces → `useTranslation(['module'])` returns empty strings at runtime. POST-CHECK 45 validates this.
117
184
  - All pages must follow loading → error → content pattern with CSS variables
118
185
 
119
186
  ### If NOT economy_mode: Agent Teams (parallel)