@atlashub/smartstack-cli 3.43.0 → 3.44.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 (42) hide show
  1. package/dist/mcp-entry.mjs +201 -22
  2. package/dist/mcp-entry.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/agents/efcore/migration.md +11 -0
  5. package/templates/agents/efcore/rebase-snapshot.md +7 -0
  6. package/templates/agents/efcore/squash.md +7 -0
  7. package/templates/skills/apex/SKILL.md +14 -9
  8. package/templates/skills/apex/_shared.md +3 -0
  9. package/templates/skills/apex/references/analysis-methods.md +1 -1
  10. package/templates/skills/apex/references/challenge-questions.md +21 -0
  11. package/templates/skills/apex/references/core-seed-data.md +59 -104
  12. package/templates/skills/apex/references/post-checks.md +289 -225
  13. package/templates/skills/apex/references/smartstack-api.md +33 -35
  14. package/templates/skills/apex/references/smartstack-frontend.md +99 -3
  15. package/templates/skills/apex/references/smartstack-layers.md +145 -23
  16. package/templates/skills/apex/steps/step-00-init.md +2 -2
  17. package/templates/skills/apex/steps/step-01-analyze.md +1 -0
  18. package/templates/skills/apex/steps/step-02-plan.md +4 -3
  19. package/templates/skills/apex/steps/step-03-execute.md +24 -24
  20. package/templates/skills/apex/steps/step-04-examine.md +64 -24
  21. package/templates/skills/apex/steps/step-05-deep-review.md +1 -1
  22. package/templates/skills/apex/steps/step-08-run-tests.md +21 -13
  23. package/templates/skills/application/references/application-roles-template.md +10 -15
  24. package/templates/skills/application/references/backend-entity-seeding.md +6 -5
  25. package/templates/skills/application/references/backend-seeding-and-dto-output.md +1 -1
  26. package/templates/skills/application/references/nav-fallback-procedure.md +14 -17
  27. package/templates/skills/application/references/provider-template.md +5 -5
  28. package/templates/skills/application/references/roles-client-project-handling.md +1 -1
  29. package/templates/skills/application/references/roles-fallback-procedure.md +10 -15
  30. package/templates/skills/application/steps/step-01-navigation.md +1 -1
  31. package/templates/skills/application/steps/step-02-permissions.md +3 -3
  32. package/templates/skills/application/steps/step-03b-provider.md +1 -0
  33. package/templates/skills/application/templates-seed.md +41 -47
  34. package/templates/skills/business-analyse/references/team-orchestration.md +2 -2
  35. package/templates/skills/controller/steps/step-04-perms.md +1 -1
  36. package/templates/skills/efcore/references/troubleshooting.md +2 -2
  37. package/templates/skills/apex/references/examine-build-validation.md +0 -82
  38. package/templates/skills/apex/references/execution-frontend-gates.md +0 -177
  39. package/templates/skills/apex/references/execution-frontend-patterns.md +0 -105
  40. package/templates/skills/apex/references/execution-layer1-rules.md +0 -96
  41. package/templates/skills/apex/references/initialization-challenge-flow.md +0 -110
  42. package/templates/skills/apex/references/planning-layer-mapping.md +0 -151
@@ -3,43 +3,34 @@
3
3
  > **Referenced by:** step-04-examine.md (section 6b)
4
4
  > These checks run on the actual generated files. Model-interpreted checks are unreliable.
5
5
 
6
- ### POST-CHECK 1: Navigation routes must be full paths starting with /
6
+ ## Run MCP Tools FIRST
7
7
 
8
- ```bash
9
- # Find all seed data files and check route values
10
- SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
11
- if [ -n "$SEED_FILES" ]; then
12
- # Check for short routes (no leading /) in Create() calls for navigation entities
13
- BAD_ROUTES=$(grep -Pn 'NavigationApplication\.Create\(|NavigationModule\.Create\(|NavigationSection\.Create\(|NavigationResource\.Create\(' $SEED_FILES | grep -v '"/[a-z]')
14
- if [ -n "$BAD_ROUTES" ]; then
15
- echo "BLOCKING: Navigation routes must be full paths starting with /"
16
- echo "$BAD_ROUTES"
17
- echo "Expected: \"/human-resources\" NOT \"humanresources\""
18
- exit 1
19
- fi
20
- fi
21
- ```
8
+ Before running these bash checks, call these 3 MCP tools (they cover ~10 additional checks automatically):
9
+
10
+ 1. `mcp__smartstack__validate_conventions()` covers: controller routes, NavRoute kebab-case, naming conventions
11
+ 2. `mcp__smartstack__validate_security()` covers: tenant isolation, TenantId filters, authorization, Guid.Empty, TenantId!.Value patterns
12
+ 3. `mcp__smartstack__validate_frontend_routes()` covers: lazy loading in routes, route alignment
13
+
14
+ **The checks below provide defense-in-depth** they catch patterns the MCP tools may miss and serve as a safety net.
15
+
16
+ ---
22
17
 
23
- ### POST-CHECK 2: All services must filter by TenantId (OWASP A01)
18
+ ## Security Tenant Isolation & Authorization (defense-in-depth with MCP)
19
+
20
+ ### POST-CHECK S1: All services must filter by TenantId (OWASP A01)
24
21
 
25
22
  ```bash
26
- # Find all service implementation files
27
23
  SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
28
24
  if [ -n "$SERVICE_FILES" ]; then
29
- # Check each service file has TenantId reference (either _currentUser.TenantId or TenantId filter)
30
25
  for f in $SERVICE_FILES; do
31
- # Accept either _currentTenant.TenantId (strict guard clause or nullable usage)
32
- # or entities with IOptionalTenantEntity/IScopedTenantEntity (optional tenant pattern)
33
26
  HAS_TENANT_FILTER=$(grep -c "TenantId" "$f")
34
27
  HAS_OPTIONAL_ENTITY=false
35
28
  if grep -q "IOptionalTenantEntity\|IScopedTenantEntity" "$f"; then
36
29
  HAS_OPTIONAL_ENTITY=true
37
30
  fi
38
-
39
31
  if [ "$HAS_TENANT_FILTER" -eq 0 ] && [ "$HAS_OPTIONAL_ENTITY" = false ]; then
40
32
  echo "BLOCKING (OWASP A01): Service missing TenantId filter or optional tenant entity: $f"
41
33
  echo "Every service query MUST filter by _currentTenant.TenantId"
42
- echo "OR work with entities that implement IOptionalTenantEntity/IScopedTenantEntity"
43
34
  exit 1
44
35
  fi
45
36
  if grep -q "Guid.Empty" "$f"; then
@@ -50,14 +41,12 @@ if [ -n "$SERVICE_FILES" ]; then
50
41
  fi
51
42
  ```
52
43
 
53
- ### POST-CHECK 3: Controllers must use [RequirePermission], not just [Authorize] (BLOCKING)
44
+ ### POST-CHECK S2: Controllers must use [RequirePermission], not just [Authorize] (BLOCKING)
54
45
 
55
46
  ```bash
56
- # Find all controller files
57
47
  CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
58
48
  if [ -n "$CTRL_FILES" ]; then
59
49
  for f in $CTRL_FILES; do
60
- # Check controller has at least one RequirePermission attribute
61
50
  if grep -q "\[Authorize\]" "$f" && ! grep -q "\[RequirePermission" "$f"; then
62
51
  echo "BLOCKING: Controller uses [Authorize] without [RequirePermission]: $f"
63
52
  echo "[Authorize] alone provides NO RBAC enforcement — any authenticated user has access"
@@ -68,38 +57,104 @@ if [ -n "$CTRL_FILES" ]; then
68
57
  fi
69
58
  ```
70
59
 
71
- ### POST-CHECK 4: Seed data must not use Guid.NewGuid()
72
-
73
- ```bash
74
- SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
75
- if [ -n "$SEED_FILES" ]; then
76
- BAD_GUIDS=$(grep -n "Guid.NewGuid()" $SEED_FILES 2>/dev/null)
77
- if [ -n "$BAD_GUIDS" ]; then
78
- echo "BLOCKING: Seed data must use deterministic GUIDs (SHA256), not Guid.NewGuid()"
79
- echo "$BAD_GUIDS"
80
- exit 1
81
- fi
82
- fi
83
- ```
84
-
85
- ### POST-CHECK 5: Services must inject ICurrentTenantService (tenant isolation)
60
+ ### POST-CHECK S3: Services must inject ICurrentTenantService (tenant isolation)
86
61
 
87
62
  ```bash
88
63
  SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
89
64
  if [ -n "$SERVICE_FILES" ]; then
90
65
  for f in $SERVICE_FILES; do
91
- # Accept either ICurrentTenantService or ICurrentUser (legacy) for tenant context
92
66
  if ! grep -qE "ICurrentTenantService|ICurrentUser" "$f"; then
93
67
  echo "BLOCKING: Service missing tenant context injection: $f"
94
68
  echo "All services MUST inject ICurrentTenantService for tenant isolation"
95
- echo "Pattern: private readonly ICurrentTenantService _currentTenant;"
96
69
  exit 1
97
70
  fi
98
71
  done
99
72
  fi
100
73
  ```
101
74
 
102
- ### POST-CHECK 6: Translation files must exist for all 4 languages (if frontend)
75
+ ### POST-CHECK S4: HasQueryFilter must not use Guid.Empty (OWASP A01)
76
+
77
+ ```bash
78
+ CONFIG_FILES=$(find src/ -path "*/Configurations/*" -name "*Configuration.cs" 2>/dev/null)
79
+ if [ -n "$CONFIG_FILES" ]; then
80
+ BAD_FILTER=$(grep -Pn 'HasQueryFilter.*Guid\.Empty' $CONFIG_FILES 2>/dev/null)
81
+ if [ -n "$BAD_FILTER" ]; then
82
+ echo "BLOCKING (OWASP A01): HasQueryFilter uses Guid.Empty — bypasses tenant isolation: $BAD_FILTER"
83
+ echo "The EF global query filter MUST use the injected TenantId, not Guid.Empty"
84
+ exit 1
85
+ fi
86
+ fi
87
+ ```
88
+
89
+ ### POST-CHECK S5: Services must NOT use TenantId!.Value (null-forgiving crash pattern)
90
+
91
+ ```bash
92
+ SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
93
+ if [ -n "$SERVICE_FILES" ]; then
94
+ BAD_PATTERN=$(grep -Pn 'TenantId!\.' $SERVICE_FILES 2>/dev/null)
95
+ if [ -n "$BAD_PATTERN" ]; then
96
+ echo "BLOCKING: TenantId!.Value (null-forgiving) detected — NullReferenceException risk: $BAD_PATTERN"
97
+ echo "Fix: Use guard clause: var tenantId = _currentTenant.TenantId ?? throw new TenantContextRequiredException();"
98
+ exit 1
99
+ fi
100
+ fi
101
+ ```
102
+
103
+ ### POST-CHECK S6: Pages must use lazy loading (no static page imports in routes)
104
+
105
+ ```bash
106
+ APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
107
+ ROUTE_FILES=$(find src/routes/ -name "*.tsx" -o -name "*.ts" 2>/dev/null)
108
+ if [ -n "$APP_TSX" ]; then
109
+ STATIC_IMPORTS=$(grep -Pn "^import .+ from '@/pages/" "$APP_TSX" $ROUTE_FILES 2>/dev/null)
110
+ if [ -n "$STATIC_IMPORTS" ]; then
111
+ echo "BLOCKING: Static page imports in route files — MUST use React.lazy()"
112
+ echo "Static imports load ALL pages upfront, killing initial load performance"
113
+ echo "$STATIC_IMPORTS"
114
+ echo "Fix: const Page = lazy(() => import('@/pages/...').then(m => ({ default: m.Page })))"
115
+ exit 1
116
+ fi
117
+ fi
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Backend — Entity, Service & Controller Checks
123
+
124
+ ### POST-CHECK 1: Navigation routes must be full paths starting with /
125
+
126
+ ```bash
127
+ # Find all seed data files and check route values
128
+ SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
129
+ if [ -n "$SEED_FILES" ]; then
130
+ # Check for short routes (no leading /) in Create() calls for navigation entities
131
+ BAD_ROUTES=$(grep -Pn 'NavigationApplication\.Create\(|NavigationModule\.Create\(|NavigationSection\.Create\(|NavigationResource\.Create\(' $SEED_FILES | grep -v '"/[a-z]')
132
+ if [ -n "$BAD_ROUTES" ]; then
133
+ echo "BLOCKING: Navigation routes must be full paths starting with /"
134
+ echo "$BAD_ROUTES"
135
+ echo "Expected: \"/human-resources\" NOT \"humanresources\""
136
+ exit 1
137
+ fi
138
+ fi
139
+ ```
140
+
141
+ ### POST-CHECK 2: Seed data must not use deterministic/sequential/fixed GUIDs
142
+
143
+ ```bash
144
+ SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
145
+ if [ -n "$SEED_FILES" ]; then
146
+ BAD_GUIDS=$(grep -Pn 'GenerateDeterministicGuid|GenerateGuid\(int|11111111-1111-1111-1111-' $SEED_FILES 2>/dev/null)
147
+ if [ -n "$BAD_GUIDS" ]; then
148
+ echo "BLOCKING: Seed data must use Guid.NewGuid(), not deterministic/sequential/fixed GUIDs"
149
+ echo "$BAD_GUIDS"
150
+ exit 1
151
+ fi
152
+ fi
153
+ ```
154
+
155
+ ## Frontend — CSS, Forms, Components, I18n
156
+
157
+ ### POST-CHECK 3: Translation files must exist for all 4 languages (if frontend)
103
158
 
104
159
  ```bash
105
160
  # Find all i18n namespaces used in tsx files
@@ -117,22 +172,7 @@ if [ -n "$TSX_FILES" ]; then
117
172
  fi
118
173
  ```
119
174
 
120
- ### POST-CHECK 7: Pages must use lazy loading (no static page imports in routes)
121
-
122
- ```bash
123
- ROUTE_FILES=$(find src/routes/ -name "*.tsx" -o -name "*.ts" 2>/dev/null)
124
- if [ -n "$ROUTE_FILES" ]; then
125
- STATIC_PAGE_IMPORTS=$(grep -Pn "^import .+ from '@/pages/" $ROUTE_FILES 2>/dev/null)
126
- if [ -n "$STATIC_PAGE_IMPORTS" ]; then
127
- echo "BLOCKING: Route files must use React.lazy() for page imports, not static imports"
128
- echo "$STATIC_PAGE_IMPORTS"
129
- echo "Fix: const Page = lazy(() => import('@/pages/...').then(m => ({ default: m.Page })));"
130
- exit 1
131
- fi
132
- fi
133
- ```
134
-
135
- ### POST-CHECK 8: Forms must be full pages with routes — ZERO modals/popups/drawers/slide-overs
175
+ ### POST-CHECK 4: Forms must be full pages with routes ZERO modals/popups/drawers/slide-overs
136
176
 
137
177
  ```bash
138
178
  # Check for modal/dialog/drawer/slide-over imports AND inline form state in page files
@@ -166,7 +206,7 @@ if [ -n "$PAGE_FILES" ]; then
166
206
  fi
167
207
  ```
168
208
 
169
- ### POST-CHECK 9: Create/Edit pages must exist as separate route pages (BLOCKING)
209
+ ### POST-CHECK 5: Create/Edit pages must exist as separate route pages (BLOCKING)
170
210
 
171
211
  ```bash
172
212
  # For each module with a list page, verify create and edit pages exist
@@ -219,7 +259,7 @@ if [ "$FAIL" = true ]; then
219
259
  fi
220
260
  ```
221
261
 
222
- ### POST-CHECK 10: Form pages must have companion test files
262
+ ### POST-CHECK 6: Form pages must have companion test files
223
263
 
224
264
  ```bash
225
265
  # Minimum requirement: if frontend pages exist, at least 1 test file must be present
@@ -248,7 +288,7 @@ if [ -n "$FORM_PAGES" ]; then
248
288
  fi
249
289
  ```
250
290
 
251
- ### POST-CHECK 11: FK fields must use EntityLookup — NO `<input>`, NO `<select>` (BLOCKING)
291
+ ### POST-CHECK 7: FK fields must use EntityLookup — NO `<input>`, NO `<select>` (BLOCKING)
252
292
 
253
293
  ```bash
254
294
  # Check ALL page files for FK fields rendered as <input> or <select> instead of EntityLookup
@@ -311,7 +351,7 @@ if [ -n "$ALL_PAGES" ]; then
311
351
  fi
312
352
  ```
313
353
 
314
- ### POST-CHECK 12: Backend APIs must support search parameter for EntityLookup
354
+ ### POST-CHECK 8: Backend APIs must support search parameter for EntityLookup
315
355
 
316
356
  ```bash
317
357
  # Check that controller GetAll methods accept search parameter
@@ -330,7 +370,7 @@ if [ -n "$CTRL_FILES" ]; then
330
370
  fi
331
371
  ```
332
372
 
333
- ### POST-CHECK 13: No hardcoded Tailwind colors in generated pages (BLOCKING)
373
+ ### POST-CHECK 9: No hardcoded Tailwind colors in generated pages (BLOCKING)
334
374
 
335
375
  ```bash
336
376
  # Scan all page and component files directly (works for uncommitted/untracked files, Windows/WSL compatible)
@@ -360,7 +400,9 @@ if [ -n "$ALL_PAGES" ]; then
360
400
  fi
361
401
  ```
362
402
 
363
- ### POST-CHECK 14: Routes seed data must match frontend application routes
403
+ ## Seed Data Navigation, Roles, Permissions
404
+
405
+ ### POST-CHECK 10: Routes seed data must match frontend application routes
364
406
 
365
407
  ```bash
366
408
  SEED_ROUTES=$(grep -Poh 'Route\s*=\s*"([^"]+)"' $(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" 2>/dev/null) 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' | sort -u)
@@ -431,24 +473,7 @@ if [ -n "$APP_TSX" ]; then
431
473
  fi
432
474
  ```
433
475
 
434
- ### POST-CHECK 15: HasQueryFilter must not use Guid.Empty (OWASP A01)
435
-
436
- ```bash
437
- CONFIG_FILES=$(find src/ -path "*/Configurations/*" -name "*Configuration.cs" 2>/dev/null)
438
- if [ -n "$CONFIG_FILES" ]; then
439
- BAD_FILTERS=$(grep -Pn 'HasQueryFilter.*Guid\.Empty' $CONFIG_FILES 2>/dev/null)
440
- if [ -n "$BAD_FILTERS" ]; then
441
- echo "BLOCKING (OWASP A01): HasQueryFilter uses Guid.Empty instead of runtime tenant isolation"
442
- echo "$BAD_FILTERS"
443
- echo ""
444
- echo "Anti-pattern: .HasQueryFilter(e => e.TenantId != Guid.Empty)"
445
- echo "Fix: Remove HasQueryFilter. Tenant isolation is handled by SmartStack base DbContext"
446
- exit 1
447
- fi
448
- fi
449
- ```
450
-
451
- ### POST-CHECK 16: GetAll methods must return PaginatedResult<T>
476
+ ### POST-CHECK 11: GetAll methods must return PaginatedResult<T>
452
477
 
453
478
  ```bash
454
479
  SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
@@ -463,7 +488,7 @@ if [ -n "$SERVICE_FILES" ]; then
463
488
  fi
464
489
  ```
465
490
 
466
- ### POST-CHECK 17: i18n files must contain required structural keys
491
+ ### POST-CHECK 12: i18n files must contain required structural keys
467
492
 
468
493
  ```bash
469
494
  I18N_DIR="src/i18n/locales/fr"
@@ -484,7 +509,7 @@ if [ -d "$I18N_DIR" ]; then
484
509
  fi
485
510
  ```
486
511
 
487
- ### POST-CHECK 18: Entities must implement IAuditableEntity + Validators must have Create/Update pairs
512
+ ### POST-CHECK 13: Entities must implement IAuditableEntity + Validators must have Create/Update pairs
488
513
 
489
514
  ```bash
490
515
  ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
@@ -512,9 +537,7 @@ if [ -n "$CREATE_VALIDATORS" ]; then
512
537
  fi
513
538
  ```
514
539
 
515
- ### POST-CHECK 19: (REMOVED Context level no longer exists in SmartStack navigation hierarchy)
516
-
517
- ### POST-CHECK 20: RolePermission seed data must NOT use deterministic role GUIDs
540
+ ### POST-CHECK 14: RolePermission seed data must NOT use deterministic role GUIDs
518
541
 
519
542
  ```bash
520
543
  # System roles (admin, manager, contributor, viewer) are pre-seeded by SmartStack core.
@@ -545,46 +568,7 @@ if [ -n "$ROLE_PERM_FILES" ]; then
545
568
  fi
546
569
  ```
547
570
 
548
- ### POST-CHECK 21: Services must NOT use TenantId!.Value (null-forgiving crash pattern)
549
-
550
- ```bash
551
- # The !.Value pattern on Guid? throws InvalidOperationException (500) instead of clean 401
552
- SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
553
- if [ -n "$SERVICE_FILES" ]; then
554
- BAD_PATTERN=$(grep -Pn 'TenantId!\s*\.Value|TenantId!\s*\.ToString|\.TenantId!' $SERVICE_FILES 2>/dev/null)
555
- if [ -n "$BAD_PATTERN" ]; then
556
- echo "BLOCKING: Services use TenantId!.Value — causes 500 instead of 400 when tenant context is missing"
557
- echo "$BAD_PATTERN"
558
- echo ""
559
- echo "Fix: Replace with guard clause at the start of every method:"
560
- echo " var tenantId = _currentTenant.TenantId"
561
- echo " ?? throw new TenantContextRequiredException();"
562
- echo ""
563
- echo "This produces a clean 400 Bad Request via GlobalExceptionHandlerMiddleware."
564
- echo "NEVER use UnauthorizedAccessException for tenant context — it returns 401 which clears the frontend token."
565
- exit 1
566
- fi
567
- fi
568
-
569
- # POST-CHECK: Services must NOT use UnauthorizedAccessException for tenant context (causes token clearing)
570
- if [ -n "$SERVICE_FILES" ]; then
571
- BAD_UNAUTH=$(grep -Pn 'UnauthorizedAccessException.*[Tt]enant' $SERVICE_FILES 2>/dev/null)
572
- if [ -n "$BAD_UNAUTH" ]; then
573
- echo "BLOCKING: Services use UnauthorizedAccessException for tenant context — causes 401 which clears the frontend token"
574
- echo "$BAD_UNAUTH"
575
- echo ""
576
- echo "Fix: Replace with:"
577
- echo " var tenantId = _currentTenant.TenantId"
578
- echo " ?? throw new TenantContextRequiredException();"
579
- echo ""
580
- echo "TenantContextRequiredException returns 400 Bad Request (does not clear token)."
581
- echo "UnauthorizedAccessException returns 401 Unauthorized (clears token + redirects to login)."
582
- exit 1
583
- fi
584
- fi
585
- ```
586
-
587
- ### POST-CHECK 22: Cross-tenant entities must use Guid? TenantId
571
+ ### POST-CHECK 15: Cross-tenant entities must use Guid? TenantId
588
572
 
589
573
  ```bash
590
574
  for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev/null); do
@@ -595,10 +579,10 @@ for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev
595
579
  fi
596
580
  fi
597
581
  done
598
- echo "POST-CHECK 22: OK"
582
+ echo "POST-CHECK 15: OK"
599
583
  ```
600
584
 
601
- ### POST-CHECK 23: Scoped entities must have EntityScope property
585
+ ### POST-CHECK 16: Scoped entities must have EntityScope property
602
586
 
603
587
  ```bash
604
588
  for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev/null); do
@@ -609,10 +593,10 @@ for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev
609
593
  fi
610
594
  fi
611
595
  done
612
- echo "POST-CHECK 23: OK"
596
+ echo "POST-CHECK 16: OK"
613
597
  ```
614
598
 
615
- ### POST-CHECK 24: Permissions.cs static constants must exist (BLOCKING)
599
+ ### POST-CHECK 17: Permissions.cs static constants must exist (BLOCKING)
616
600
 
617
601
  ```bash
618
602
  # Every module with controllers MUST have a Permissions.cs with static constants
@@ -630,7 +614,7 @@ if [ -n "$CTRL_FILES" ]; then
630
614
  fi
631
615
  ```
632
616
 
633
- ### POST-CHECK 25: ApplicationRolesSeedData.cs must exist (BLOCKING)
617
+ ### POST-CHECK 18: ApplicationRolesSeedData.cs must exist (BLOCKING)
634
618
 
635
619
  ```bash
636
620
  # If any RolesSeedData exists, ApplicationRolesSeedData MUST also exist
@@ -668,14 +652,14 @@ if [ -n "$SECTION_SEED_FILES" ] && [ -n "$APP_TSX" ]; then
668
652
  done
669
653
  fi
670
654
 
671
- # Controllers with section-level [NavRoute] (4 segments) must have matching [RequirePermission]
655
+ # All controllers with [NavRoute] must have matching [RequirePermission]
672
656
  CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
673
657
  if [ -n "$CTRL_FILES" ]; then
674
658
  for f in $CTRL_FILES; do
675
- # Match NavRoute with 4 dot-separated segments (section-level)
676
- SECTION_NAVROUTE=$(grep -oP 'NavRoute\("[a-z]+\.[a-z]+\.[a-z]+\.[a-z]+"\)' "$f" 2>/dev/null)
659
+ # Match NavRoute with 2+ dot-separated segments (minimum: app.module)
660
+ SECTION_NAVROUTE=$(grep -oP 'NavRoute\("[a-z-]+\.[a-z-]+' "$f" 2>/dev/null)
677
661
  if [ -n "$SECTION_NAVROUTE" ] && ! grep -q "\[RequirePermission" "$f"; then
678
- echo "BLOCKING: Section controller has [NavRoute] but no [RequirePermission]: $f"
662
+ echo "BLOCKING: Controller has [NavRoute] but no [RequirePermission]: $f"
679
663
  echo "Fix: Add [RequirePermission(Permissions.{Section}.{Action})] on each endpoint"
680
664
  exit 1
681
665
  fi
@@ -690,13 +674,13 @@ if [ -n "$SECTION_SEED_FILES" ] && [ -n "$PERM_FILE" ]; then
690
674
  PASCAL=$(echo "$CODE" | sed 's/^./\U&/')
691
675
  if ! grep -q "static class $PASCAL" "$PERM_FILE" 2>/dev/null; then
692
676
  echo "WARNING: Section '$CODE' in seed data has no matching Permissions.$PASCAL static class"
693
- echo "Fix: Add section-level permissions via MCP generate_permissions with 4-segment navRoute"
677
+ echo "Fix: Add section-level permissions via MCP generate_permissions with 3-segment navRoute (app.module.section)"
694
678
  fi
695
679
  done
696
680
  fi
697
681
  ```
698
682
 
699
- ### POST-CHECK 26: FORBIDDEN route patterns — /list and /detail/:id (BLOCKING)
683
+ ### POST-CHECK 19: FORBIDDEN route patterns — /list and /detail/:id (BLOCKING)
700
684
 
701
685
  ```bash
702
686
  # 1. Check seed data for FORBIDDEN suffixes
@@ -727,7 +711,7 @@ fi
727
711
  echo "OK: No forbidden /list or /detail route patterns found"
728
712
  ```
729
713
 
730
- ### POST-CHECK 27: Permission path segment count (WARNING)
714
+ ### POST-CHECK 20: Permission path segment count (WARNING)
731
715
 
732
716
  ```bash
733
717
  PERM_FILES=$(find src/ -path "*/Seeding/Data/*" -name "PermissionsSeedData.cs" 2>/dev/null)
@@ -749,7 +733,7 @@ if [ -n "$PERM_FILES" ]; then
749
733
  fi
750
734
  ```
751
735
 
752
- ### POST-CHECK 28: IClientSeedDataProvider must have 4 methods + DI registration (BLOCKING)
736
+ ### POST-CHECK 21: IClientSeedDataProvider must have 4 methods + DI registration (BLOCKING)
753
737
 
754
738
  ```bash
755
739
  PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
@@ -779,7 +763,7 @@ if [ -n "$PROVIDER" ]; then
779
763
  fi
780
764
  ```
781
765
 
782
- ### POST-CHECK 29: i18n must use separate JSON files per language (not embedded in index.ts)
766
+ ### POST-CHECK 22: i18n must use separate JSON files per language (not embedded in index.ts)
783
767
 
784
768
  ```bash
785
769
  # Translations MUST be in src/i18n/locales/{lang}/{module}.json, NOT embedded in a single .ts file
@@ -829,7 +813,7 @@ if [ -n "$TSX_FILES" ]; then
829
813
  fi
830
814
  ```
831
815
 
832
- ### POST-CHECK 30: Pages must use useTranslation hook (no hardcoded user-facing strings)
816
+ ### POST-CHECK 23: Pages must use useTranslation hook (no hardcoded user-facing strings)
833
817
 
834
818
  ```bash
835
819
  # Verify that page components use i18n — detect hardcoded strings in JSX
@@ -858,7 +842,7 @@ if [ -n "$PAGE_FILES" ]; then
858
842
  fi
859
843
  ```
860
844
 
861
- ### POST-CHECK 31: List/Detail pages must include DocToggleButton (documentation panel)
845
+ ### POST-CHECK 24: List/Detail pages must include DocToggleButton (documentation panel)
862
846
 
863
847
  ```bash
864
848
  # Every list and detail page MUST have DocToggleButton for inline documentation access
@@ -881,7 +865,7 @@ if [ -n "$LIST_PAGES" ]; then
881
865
  fi
882
866
  ```
883
867
 
884
- ### POST-CHECK 32: Module documentation must be generated (doc-data.ts)
868
+ ### POST-CHECK 25: Module documentation must be generated (doc-data.ts)
885
869
 
886
870
  ```bash
887
871
  # After frontend pages exist, /documentation should have been called
@@ -894,7 +878,7 @@ if [ -n "$TSX_PAGES" ] && [ -z "$DOC_DATA" ]; then
894
878
  fi
895
879
  ```
896
880
 
897
- ### POST-CHECK 33: Pagination type must be PaginatedResult<T> — no aliases (BLOCKING)
881
+ ### POST-CHECK 26: Pagination type must be PaginatedResult<T> — no aliases (BLOCKING)
898
882
 
899
883
  ```bash
900
884
  SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
@@ -912,7 +896,9 @@ if [ -n "$(echo $ALL_FILES | tr -d ' ')" ]; then
912
896
  fi
913
897
  ```
914
898
 
915
- ### POST-CHECK 34: Code generation ICodeGenerator must be registered for auto-generated entities (BLOCKING)
899
+ ## InfrastructureMigration & Build
900
+
901
+ ### POST-CHECK 27: Code generation — ICodeGenerator must be registered for auto-generated entities (BLOCKING)
916
902
 
917
903
  ```bash
918
904
  # If feature.json has entities with codePattern.strategy != "manual",
@@ -943,7 +929,7 @@ except: pass
943
929
  fi
944
930
  ```
945
931
 
946
- ### POST-CHECK 35: Code regex must support hyphens (BLOCKING)
932
+ ### POST-CHECK 28: Code regex must support hyphens (BLOCKING)
947
933
 
948
934
  ```bash
949
935
  VALIDATOR_FILES=$(find src/ -path "*/Validators/*" -name "*Validator.cs" 2>/dev/null)
@@ -958,7 +944,7 @@ if [ -n "$VALIDATOR_FILES" ]; then
958
944
  fi
959
945
  ```
960
946
 
961
- ### POST-CHECK 36: CreateDto must NOT have Code field when service uses ICodeGenerator (WARNING)
947
+ ### POST-CHECK 29: CreateDto must NOT have Code field when service uses ICodeGenerator (WARNING)
962
948
 
963
949
  ```bash
964
950
  SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
@@ -976,7 +962,7 @@ if [ -n "$SERVICE_FILES" ]; then
976
962
  fi
977
963
  ```
978
964
 
979
- ### POST-CHECK 37: Translation seed data must have idempotency guard (BLOCKING)
965
+ ### POST-CHECK 30: Translation seed data must have idempotency guard (BLOCKING)
980
966
 
981
967
  ```bash
982
968
  PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
@@ -999,16 +985,16 @@ if [ -n "$PROVIDER" ]; then
999
985
  fi
1000
986
  ```
1001
987
 
1002
- ### POST-CHECK 38: Resource seed data must use actual section IDs from DB (BLOCKING)
988
+ ### POST-CHECK 31: Resource seed data must use actual section IDs from DB (BLOCKING)
1003
989
 
1004
990
  ```bash
1005
991
  PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
1006
992
  if [ -n "$PROVIDER" ]; then
1007
- # Check if NavigationResource.Create uses secEntry.Id or resEntry.SectionId (deterministic GUIDs)
993
+ # Check if NavigationResource.Create uses secEntry.Id or resEntry.SectionId (seed-time GUIDs)
1008
994
  # instead of actualSection.Id (real DB ID). This causes FK_nav_Resources_nav_Sections_SectionId violation.
1009
995
  if grep -Pn 'NavigationResource\.Create\(' "$PROVIDER" | grep -q 'resEntry\.SectionId\|secEntry\.Id'; then
1010
- echo "BLOCKING: Resource seed data uses deterministic GUID as SectionId in $PROVIDER"
1011
- echo "NavigationSection.Create() generates its own ID — deterministic seed GUIDs do NOT exist in nav_Sections."
996
+ echo "BLOCKING: Resource seed data uses seed-time GUID as SectionId in $PROVIDER"
997
+ echo "NavigationSection.Create() generates its own ID — seed-time GUIDs do NOT exist in nav_Sections."
1012
998
  echo "Fix: Query actual section from DB before creating resources:"
1013
999
  echo " var actualSection = await context.NavigationSections"
1014
1000
  echo " .FirstAsync(s => s.Code == secEntry.Code && s.ModuleId == modEntity.Id, ct);"
@@ -1018,28 +1004,7 @@ if [ -n "$PROVIDER" ]; then
1018
1004
  fi
1019
1005
  ```
1020
1006
 
1021
- ### POST-CHECK 39: Controllers must NOT have [Route] alongside [NavRoute] (BLOCKING)
1022
-
1023
- ```bash
1024
- # [NavRoute] REPLACES [Route] — it resolves HTTP routes from the navigation DB at startup.
1025
- # Having both is redundant and may cause route conflicts.
1026
- CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
1027
- if [ -n "$CTRL_FILES" ]; then
1028
- for f in $CTRL_FILES; do
1029
- HAS_NAVROUTE=$(grep -P '\[NavRoute\(' "$f" 2>/dev/null)
1030
- HAS_ROUTE=$(grep -P '\[Route\(' "$f" 2>/dev/null)
1031
- if [ -n "$HAS_NAVROUTE" ] && [ -n "$HAS_ROUTE" ]; then
1032
- echo "BLOCKING: Controller has both [Route] and [NavRoute] — [NavRoute] replaces [Route]: $f"
1033
- echo " Found [NavRoute]: $HAS_NAVROUTE"
1034
- echo " Found [Route]: $HAS_ROUTE"
1035
- echo "Fix: Remove the [Route(\"api/...\")] attribute. [NavRoute] resolves routes from navigation DB at startup."
1036
- exit 1
1037
- fi
1038
- done
1039
- fi
1040
- ```
1041
-
1042
- ### POST-CHECK 40: NavRoute segments must use kebab-case for multi-word codes (BLOCKING)
1007
+ ### POST-CHECK 32: NavRoute segments must use kebab-case for multi-word codes (BLOCKING)
1043
1008
 
1044
1009
  ```bash
1045
1010
  # NavRoute segments are navigation entity Codes joined by dots.
@@ -1078,7 +1043,7 @@ if [ -n "$SEED_FILES" ]; then
1078
1043
  fi
1079
1044
  ```
1080
1045
 
1081
- ### POST-CHECK 41: Permission codes must use kebab-case matching NavRoute codes (BLOCKING)
1046
+ ### POST-CHECK 33: Permission codes must use kebab-case matching NavRoute codes (BLOCKING)
1082
1047
 
1083
1048
  ```bash
1084
1049
  # Permission codes in [RequirePermission] and Permissions.cs MUST use kebab-case for multi-word segments.
@@ -1143,7 +1108,7 @@ if [ -n "$SEED_PERM_FILES" ]; then
1143
1108
  fi
1144
1109
  ```
1145
1110
 
1146
- ### POST-CHECK 42: Frontend navigate() calls must have matching route definitions (BLOCKING)
1111
+ ### POST-CHECK 34: Frontend navigate() calls must have matching route definitions (BLOCKING)
1147
1112
 
1148
1113
  ```bash
1149
1114
  # Detect dead links: navigate() calls to paths that don't have corresponding page components.
@@ -1182,7 +1147,7 @@ if [ -n "$PAGE_FILES" ]; then
1182
1147
  fi
1183
1148
  ```
1184
1149
 
1185
- ### POST-CHECK 43: Detail page tabs must NOT navigate() — content switches locally (BLOCKING)
1150
+ ### POST-CHECK 35: Detail page tabs must NOT navigate() — content switches locally (BLOCKING)
1186
1151
 
1187
1152
  ```bash
1188
1153
  # Tabs on detail pages MUST use local state (setActiveTab) — NEVER navigate() to other pages.
@@ -1223,7 +1188,7 @@ if [ -n "$DETAIL_PAGES" ]; then
1223
1188
  fi
1224
1189
  ```
1225
1190
 
1226
- ### POST-CHECK 44: Migration ModelSnapshot must contain ALL entities registered in DbContext (BLOCKING)
1191
+ ### POST-CHECK 36: Migration ModelSnapshot must contain ALL entities registered in DbContext (BLOCKING)
1227
1192
 
1228
1193
  ```bash
1229
1194
  # Root cause (test-apex-007): 7 entities registered in DbContext but migration only covered 3.
@@ -1258,7 +1223,7 @@ if [ -n "$SNAPSHOT" ] && [ -n "$DBCONTEXT" ]; then
1258
1223
  fi
1259
1224
  ```
1260
1225
 
1261
- ### POST-CHECK 45: I18n namespace files must be registered in i18n config (BLOCKING)
1226
+ ### POST-CHECK 37: I18n namespace files must be registered in i18n config (BLOCKING)
1262
1227
 
1263
1228
  ```bash
1264
1229
  # Root cause (test-apex-007): i18n JSON files existed in src/i18n/locales/ but were never
@@ -1287,7 +1252,7 @@ if [ -n "$I18N_CONFIG" ]; then
1287
1252
  fi
1288
1253
  ```
1289
1254
 
1290
- ### POST-CHECK 46: FluentValidation validators must be registered via DI (BLOCKING)
1255
+ ### POST-CHECK 38: FluentValidation validators must be registered via DI (BLOCKING)
1291
1256
 
1292
1257
  ```bash
1293
1258
  # Root cause (test-apex-007): Validators existed but were never registered in DI.
@@ -1322,7 +1287,7 @@ if [ -n "$VALIDATOR_FILES" ]; then
1322
1287
  fi
1323
1288
  ```
1324
1289
 
1325
- ### POST-CHECK 47: Date/date properties in DTOs must use DateOnly, not string (BLOCKING)
1290
+ ### POST-CHECK 39: Date/date properties in DTOs must use DateOnly, not string (BLOCKING)
1326
1291
 
1327
1292
  ```bash
1328
1293
  # Root cause (test-apex-007): WorkLog DTO had Date property typed as string instead of DateOnly.
@@ -1347,45 +1312,10 @@ if [ -n "$DTO_FILES" ]; then
1347
1312
  fi
1348
1313
  ```
1349
1314
 
1350
- ### POST-CHECK 48: NavRoute attribute values must use kebab-case (BLOCKING)
1315
+ ### POST-CHECK 40: Every module with entities must have a migration covering them (BLOCKING)
1351
1316
 
1352
1317
  ```bash
1353
- # Root cause (test-apex-007): Controllers had [NavRoute("humanresources.employees")]
1354
- # instead of [NavRoute("human-resources.employees")]. This causes route mismatch with
1355
- # seed data and permission codes, resulting in 404s at runtime.
1356
- CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
1357
- if [ -n "$CTRL_FILES" ]; then
1358
- FAIL=false
1359
- for f in $CTRL_FILES; do
1360
- NAVROUTE_VALS=$(grep -oP 'NavRoute\("([^"]+)"' "$f" 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"')
1361
- for NR in $NAVROUTE_VALS; do
1362
- # Check each segment for concatenated multi-word without hyphens
1363
- SEGMENTS=$(echo "$NR" | tr '.' '\n')
1364
- for SEG in $SEGMENTS; do
1365
- # Detect segments that look like concatenated words (lowercase, 8+ chars, no hyphens)
1366
- # Use a simpler heuristic: lowercase-only segment with known multi-word patterns
1367
- if echo "$SEG" | grep -qP '^[a-z]{8,}$'; then
1368
- # Additional check: does it contain a known multi-word pattern?
1369
- if echo "$SEG" | grep -qP '(human|project|leave|client|support|email|time|work|resource)'; then
1370
- echo "BLOCKING: NavRoute segment '$SEG' in $f appears to be concatenated multi-word without hyphens"
1371
- echo " Full NavRoute: $NR"
1372
- echo " Fix: Use kebab-case: e.g., 'humanresources' → 'human-resources', 'projectmanagement' → 'project-management'"
1373
- FAIL=true
1374
- fi
1375
- fi
1376
- done
1377
- done
1378
- done
1379
- if [ "$FAIL" = true ]; then
1380
- exit 1
1381
- fi
1382
- fi
1383
- ```
1384
-
1385
- ### POST-CHECK 49: Every module with entities must have a migration covering them (BLOCKING)
1386
-
1387
- ```bash
1388
- # Complementary to POST-CHECK 44 — checks from the entity side.
1318
+ # Complementary to POST-CHECK 36 checks from the entity side.
1389
1319
  # Finds entity .cs files in Domain/ and verifies they appear in at least one migration file.
1390
1320
  ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null | grep -v test)
1391
1321
  MIGRATION_DIR=$(find src/ -path "*/Migrations" -type d 2>/dev/null | head -1)
@@ -1415,7 +1345,7 @@ if [ -n "$ENTITY_FILES" ] && [ -n "$MIGRATION_DIR" ]; then
1415
1345
  fi
1416
1346
  ```
1417
1347
 
1418
- ### POST-CHECK 50: Controllers must NOT have both [Route] and [NavRoute] attributes (BLOCKING)
1348
+ ### POST-CHECK 41: Controllers must NOT have both [Route] and [NavRoute] attributes (BLOCKING)
1419
1349
 
1420
1350
  ```bash
1421
1351
  # Root cause (test-apex-007): All 7 controllers had BOTH [Route("api/...")] and [NavRoute("...")].
@@ -1448,7 +1378,7 @@ if [ -n "$CTRL_FILES" ]; then
1448
1378
  fi
1449
1379
  ```
1450
1380
 
1451
- ### POST-CHECK 51: RolesSeedData must map standard role-permission matrix (BLOCKING)
1381
+ ### POST-CHECK 42: RolesSeedData must map standard role-permission matrix (BLOCKING)
1452
1382
 
1453
1383
  ```bash
1454
1384
  # SmartStack standard role-permission matrix:
@@ -1498,7 +1428,7 @@ if [ -n "$ROLE_SEED_FILES" ]; then
1498
1428
  fi
1499
1429
  ```
1500
1430
 
1501
- ### POST-CHECK 52: PermissionAction enum must use valid typed values only (BLOCKING)
1431
+ ### POST-CHECK 43: PermissionAction enum must use valid typed values only (BLOCKING)
1502
1432
 
1503
1433
  ```bash
1504
1434
  # Valid PermissionAction enum values: Access(0), Read(1), Create(2), Update(3), Delete(4),
@@ -1533,7 +1463,7 @@ if [ -n "$SEED_FILES" ]; then
1533
1463
  fi
1534
1464
  ```
1535
1465
 
1536
- ### POST-CHECK 53: Navigation translation completeness — 4 languages per level (BLOCKING)
1466
+ ### POST-CHECK 44: Navigation translation completeness — 4 languages per level (BLOCKING)
1537
1467
 
1538
1468
  ```bash
1539
1469
  # Every navigation seed data file must provide translations for ALL 4 languages (fr, en, it, de).
@@ -1581,4 +1511,138 @@ if [ -n "$NAV_SEED_FILES" ]; then
1581
1511
  fi
1582
1512
  ```
1583
1513
 
1584
- **If ANY POST-CHECK fails → fix in step-03, re-validate.**
1514
+ ---
1515
+
1516
+ ## Architecture — Clean Architecture Layer Isolation
1517
+
1518
+ ### POST-CHECK A1: Domain must not import other layers (BLOCKING)
1519
+
1520
+ ```bash
1521
+ DOMAIN_FILES=$(find src/ -path "*/Domain/*" -name "*.cs" 2>/dev/null)
1522
+ if [ -n "$DOMAIN_FILES" ]; then
1523
+ BAD_IMPORTS=$(grep -Pn 'using\s+[\w.]*\.(Application|Infrastructure|Api)[\w.]*;' $DOMAIN_FILES 2>/dev/null)
1524
+ if [ -n "$BAD_IMPORTS" ]; then
1525
+ echo "BLOCKING: Domain layer imports Application/Infrastructure/Api — violates Clean Architecture"
1526
+ echo "Domain is the core, it must not depend on any other layer"
1527
+ echo "$BAD_IMPORTS"
1528
+ echo "Fix: Move shared types to Domain or remove the dependency"
1529
+ exit 1
1530
+ fi
1531
+ fi
1532
+ ```
1533
+
1534
+ ### POST-CHECK A2: Application must not import Infrastructure or Api (BLOCKING)
1535
+
1536
+ ```bash
1537
+ APP_FILES=$(find src/ -path "*/Application/*" -name "*.cs" 2>/dev/null)
1538
+ if [ -n "$APP_FILES" ]; then
1539
+ BAD_IMPORTS=$(grep -Pn 'using\s+[\w.]*\.(Infrastructure|Api)[\w.]*;' $APP_FILES 2>/dev/null)
1540
+ if [ -n "$BAD_IMPORTS" ]; then
1541
+ echo "BLOCKING: Application layer imports Infrastructure/Api — violates Clean Architecture"
1542
+ echo "Application defines interfaces, Infrastructure implements them"
1543
+ echo "$BAD_IMPORTS"
1544
+ echo "Fix: Define an interface in Application and implement it in Infrastructure"
1545
+ exit 1
1546
+ fi
1547
+ fi
1548
+ ```
1549
+
1550
+ ### POST-CHECK A3: Controllers must not inject DbContext (BLOCKING)
1551
+
1552
+ ```bash
1553
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
1554
+ if [ -n "$CTRL_FILES" ]; then
1555
+ BAD_DBCONTEXT=$(grep -Pn 'private\s+readonly\s+\w*DbContext|DbContext\s+\w+[,)]' $CTRL_FILES 2>/dev/null)
1556
+ if [ -n "$BAD_DBCONTEXT" ]; then
1557
+ echo "BLOCKING: Controller injects DbContext directly — violates Clean Architecture"
1558
+ echo "Controllers must use Application services, not access the database directly"
1559
+ echo "$BAD_DBCONTEXT"
1560
+ echo "Fix: Create an Application service with the required business logic and inject it instead"
1561
+ exit 1
1562
+ fi
1563
+ fi
1564
+ ```
1565
+
1566
+ ### POST-CHECK A4: API must return DTOs, not Domain entities (WARNING)
1567
+
1568
+ ```bash
1569
+ # Scan controller return types for Domain entity names without Dto/Response/ViewModel suffix
1570
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
1571
+ ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
1572
+ if [ -n "$CTRL_FILES" ] && [ -n "$ENTITY_FILES" ]; then
1573
+ ENTITY_NAMES=$(grep -ohP 'public\s+class\s+(\w+)\s*:' $ENTITY_FILES 2>/dev/null | grep -oP '\w+(?=\s*:)' | grep -v '^public$' | sort -u)
1574
+ for ENTITY in $ENTITY_NAMES; do
1575
+ BAD_RETURN=$(grep -Pn "ActionResult<$ENTITY>|ActionResult<IEnumerable<$ENTITY>>|ActionResult<List<$ENTITY>>" $CTRL_FILES 2>/dev/null)
1576
+ if [ -n "$BAD_RETURN" ]; then
1577
+ echo "WARNING: Controller returns Domain entity '$ENTITY' instead of a DTO"
1578
+ echo "$BAD_RETURN"
1579
+ echo "Fix: Return ${ENTITY}ResponseDto instead of $ENTITY"
1580
+ fi
1581
+ done
1582
+ fi
1583
+ ```
1584
+
1585
+ ### POST-CHECK A5: Service interfaces in Application, implementations in Infrastructure (WARNING)
1586
+
1587
+ ```bash
1588
+ # Check for service implementations (non-interface classes) in Application or Api layers
1589
+ APP_SERVICES=$(find src/ -path "*/Application/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
1590
+ if [ -n "$APP_SERVICES" ]; then
1591
+ for f in $APP_SERVICES; do
1592
+ if grep -q 'public class.*Service' "$f" 2>/dev/null; then
1593
+ echo "WARNING: Service implementation found in Application layer: $f"
1594
+ echo "Fix: Move implementation to Infrastructure/Services/. Application should only contain interfaces."
1595
+ fi
1596
+ done
1597
+ fi
1598
+
1599
+ # Check for service interfaces (I*Service) in Domain or Api layers
1600
+ DOMAIN_INTERFACES=$(find src/ -path "*/Domain/*" -name "I*Service.cs" 2>/dev/null)
1601
+ API_INTERFACES=$(find src/ -path "*/Api/*" -name "I*Service.cs" 2>/dev/null)
1602
+ for f in $DOMAIN_INTERFACES $API_INTERFACES; do
1603
+ if [ -n "$f" ] && grep -q 'public interface.*Service' "$f" 2>/dev/null; then
1604
+ echo "WARNING: Service interface found outside Application layer: $f"
1605
+ echo "Fix: Move to Application/Interfaces/"
1606
+ fi
1607
+ done
1608
+ ```
1609
+
1610
+ ### POST-CHECK A6: No EF Core attributes in Domain entities (BLOCKING)
1611
+
1612
+ ```bash
1613
+ DOMAIN_FILES=$(find src/ -path "*/Domain/*" -name "*.cs" 2>/dev/null)
1614
+ if [ -n "$DOMAIN_FILES" ]; then
1615
+ BAD_EF=$(grep -Pn '\[Table\(|\[Column\(|\[Index\(|using\s+Microsoft\.EntityFrameworkCore' $DOMAIN_FILES 2>/dev/null)
1616
+ if [ -n "$BAD_EF" ]; then
1617
+ echo "BLOCKING: EF Core attributes or using directives found in Domain layer"
1618
+ echo "Domain entities must be persistence-ignorant — EF configuration belongs in Infrastructure"
1619
+ echo "$BAD_EF"
1620
+ echo "Fix: Move [Table], [Column], [Index] to IEntityTypeConfiguration<T> in Infrastructure/Persistence/Configurations/"
1621
+ exit 1
1622
+ fi
1623
+ fi
1624
+ ```
1625
+
1626
+ ### POST-CHECK A7: No direct repository usage in controllers (WARNING)
1627
+
1628
+ ```bash
1629
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
1630
+ if [ -n "$CTRL_FILES" ]; then
1631
+ BAD_REPO=$(grep -Pn 'IRepository<|IGenericRepository<|private\s+readonly\s+IRepository|private\s+readonly\s+IGenericRepository' $CTRL_FILES 2>/dev/null)
1632
+ if [ -n "$BAD_REPO" ]; then
1633
+ echo "WARNING: Controller injects repository directly — should use Application services"
1634
+ echo "$BAD_REPO"
1635
+ echo "Fix: Controllers should depend on Application services (I*Service), not repositories"
1636
+ fi
1637
+ fi
1638
+ ```
1639
+
1640
+ ---
1641
+
1642
+ ## Summary
1643
+
1644
+ After running all checks above, report the total count:
1645
+ - **N BLOCKING** issues must be fixed before committing
1646
+ - **M WARNING** issues should be reviewed but are non-blocking
1647
+
1648
+ **If ANY POST-CHECK fails → fix in step-06, re-validate.**