@atlashub/smartstack-cli 4.32.0 → 4.34.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 (46) hide show
  1. package/.documentation/index.html +2 -2
  2. package/.documentation/init.html +358 -174
  3. package/dist/index.js +45 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/mcp-entry.mjs +271 -44
  6. package/dist/mcp-entry.mjs.map +1 -1
  7. package/package.json +1 -1
  8. package/templates/mcp-scaffolding/controller.cs.hbs +54 -128
  9. package/templates/project/README.md +19 -0
  10. package/templates/project/claude-md/api.CLAUDE.md.template +315 -0
  11. package/templates/project/claude-md/application.CLAUDE.md.template +181 -0
  12. package/templates/project/claude-md/domain.CLAUDE.md.template +125 -0
  13. package/templates/project/claude-md/infrastructure.CLAUDE.md.template +168 -0
  14. package/templates/project/claude-md/root.CLAUDE.md.template +339 -0
  15. package/templates/project/claude-md/web.CLAUDE.md.template +339 -0
  16. package/templates/skills/apex/SKILL.md +16 -10
  17. package/templates/skills/apex/_shared.md +1 -1
  18. package/templates/skills/apex/references/checks/architecture-checks.sh +154 -0
  19. package/templates/skills/apex/references/checks/backend-checks.sh +194 -0
  20. package/templates/skills/apex/references/checks/frontend-checks.sh +448 -0
  21. package/templates/skills/apex/references/checks/infrastructure-checks.sh +255 -0
  22. package/templates/skills/apex/references/checks/security-checks.sh +153 -0
  23. package/templates/skills/apex/references/checks/seed-checks.sh +536 -0
  24. package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +49 -192
  25. package/templates/skills/apex/references/post-checks.md +124 -2156
  26. package/templates/skills/apex/references/smartstack-api.md +160 -957
  27. package/templates/skills/apex/references/smartstack-frontend.md +134 -1022
  28. package/templates/skills/apex/references/smartstack-layers.md +12 -6
  29. package/templates/skills/apex/steps/step-00-init.md +81 -238
  30. package/templates/skills/apex/steps/step-03-execute.md +25 -752
  31. package/templates/skills/apex/steps/step-03a-layer0-domain.md +118 -0
  32. package/templates/skills/apex/steps/step-03b-layer1-seed.md +91 -0
  33. package/templates/skills/apex/steps/step-03c-layer2-backend.md +240 -0
  34. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +300 -0
  35. package/templates/skills/apex/steps/step-03e-layer4-devdata.md +44 -0
  36. package/templates/skills/apex/steps/step-04-examine.md +70 -150
  37. package/templates/skills/application/references/frontend-i18n-and-output.md +2 -2
  38. package/templates/skills/application/references/frontend-route-naming.md +5 -1
  39. package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +49 -198
  40. package/templates/skills/application/references/frontend-verification.md +11 -11
  41. package/templates/skills/application/steps/step-05-frontend.md +26 -15
  42. package/templates/skills/application/templates-frontend.md +4 -0
  43. package/templates/skills/cli-app-sync/SKILL.md +2 -2
  44. package/templates/skills/cli-app-sync/references/comparison-map.md +1 -1
  45. package/templates/skills/controller/references/controller-code-templates.md +70 -67
  46. package/templates/skills/controller/references/mcp-scaffold-workflow.md +5 -1
@@ -0,0 +1,536 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # POST-CHECK: Seed Data — Navigation, Roles, Permissions
5
+ # C1-C2, C10, C15-C23, C32-C35, C44-C48, C53: Navigation completeness, role matrix, idempotency, translations
6
+
7
+ FAIL=false
8
+
9
+ # POST-CHECK C1: Navigation routes must be full paths starting with /
10
+ SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
11
+ if [ -n "$SEED_FILES" ]; then
12
+ BAD_ROUTES=$(grep -Pn 'NavigationApplication\.Create\(|NavigationModule\.Create\(|NavigationSection\.Create\(|NavigationResource\.Create\(' $SEED_FILES | grep -v '"/[a-z]' || true)
13
+ if [ -n "$BAD_ROUTES" ]; then
14
+ echo "WARNING: Navigation routes must be full paths starting with /"
15
+ echo "$BAD_ROUTES"
16
+ echo "Expected: \"/human-resources\" NOT \"humanresources\""
17
+ fi
18
+ fi
19
+
20
+ # POST-CHECK C2: Seed data must not use deterministic/sequential/fixed GUIDs
21
+ SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
22
+ if [ -n "$SEED_FILES" ]; then
23
+ BAD_GUIDS=$(grep -Pn 'GenerateDeterministicGuid|GenerateGuid\(int|11111111-1111-1111-1111-' $SEED_FILES 2>/dev/null || true)
24
+ if [ -n "$BAD_GUIDS" ]; then
25
+ echo "WARNING: Seed data must use Guid.NewGuid(), not deterministic/sequential/fixed GUIDs"
26
+ echo "$BAD_GUIDS"
27
+ fi
28
+ fi
29
+
30
+ # POST-CHECK C10: Routes seed data must match frontend application routes
31
+ 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 || true)
32
+ APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
33
+ if [ -n "$APP_TSX" ] && [ -n "$SEED_ROUTES" ]; then
34
+ FRONTEND_PATHS=$(grep -oP "path:\s*'([^']+)'" "$APP_TSX" | grep -oP "'[^']+'" | tr -d "'" | sort -u || true)
35
+ if [ -n "$FRONTEND_PATHS" ]; then
36
+ MISMATCH_FOUND=false
37
+ for SEED_ROUTE in $SEED_ROUTES; do
38
+ DEPTH=$(echo "$SEED_ROUTE" | tr '/' '\n' | grep -c '.')
39
+ if [ "$DEPTH" -lt 3 ]; then continue; fi
40
+ SEED_SUFFIX=$(echo "$SEED_ROUTE" | sed 's|^/[^/]*/||')
41
+ SEED_NORM=$(echo "$SEED_SUFFIX" | tr '[:upper:]' '[:lower:]' | tr -d '-')
42
+ MATCH_FOUND=false
43
+ for FE_PATH in $FRONTEND_PATHS; do
44
+ if echo "$FE_PATH" | grep -qP '/list$'; then
45
+ echo "WARNING: Frontend route ends with /list — should use index route instead: $FE_PATH"
46
+ fi
47
+ FE_BASE=$(echo "$FE_PATH" | sed 's|/list$||;s|/new$||;s|/:id.*||;s|/create$||')
48
+ FE_NORM=$(echo "$FE_BASE" | tr '[:upper:]' '[:lower:]' | tr -d '-')
49
+ if [ "$SEED_NORM" = "$FE_NORM" ]; then
50
+ MATCH_FOUND=true
51
+ break
52
+ fi
53
+ done
54
+ if [ "$MATCH_FOUND" = false ]; then
55
+ echo "CRITICAL: Seed data route has no matching frontend route: $SEED_ROUTE"
56
+ MISMATCH_FOUND=true
57
+ fi
58
+ done
59
+ if [ "$MISMATCH_FOUND" = true ]; then
60
+ echo "Fix: Ensure every NavigationSeedData route has a corresponding PageRegistry.register() entry in componentRegistry.generated.ts (or route entry in App.tsx for legacy projects)"
61
+ FAIL=true
62
+ fi
63
+ fi
64
+ fi
65
+
66
+ # POST-CHECK C15: RolePermission seed data must NOT use deterministic role GUIDs
67
+ SEED_ALL_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
68
+ SEED_CONST_FILES=$(find src/ -path "*/Seeding/*" -name "SeedConstants.cs" 2>/dev/null)
69
+ if [ -n "$SEED_ALL_FILES" ]; then
70
+ BAD_ROLE_GUID=$(grep -Pn 'DeterministicGuid\("role:' $SEED_ALL_FILES $SEED_CONST_FILES 2>/dev/null || true)
71
+ if [ -n "$BAD_ROLE_GUID" ]; then
72
+ echo "WARNING: Deterministic GUID for role detected (e.g., DeterministicGuid(\"role:admin\"))"
73
+ echo "System roles are pre-seeded by SmartStack core with their own IDs"
74
+ echo "Fix: In SeedRolePermissionsAsync(), look up roles by Code:"
75
+ echo " var roles = await context.Roles.Where(r => r.IsSystem || r.ApplicationId != null).ToListAsync(ct);"
76
+ echo " var role = roles.FirstOrDefault(r => r.Code == mapping.RoleCode);"
77
+ echo "$BAD_ROLE_GUID"
78
+ fi
79
+ fi
80
+
81
+ ROLE_PERM_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*RolesSeedData.cs" 2>/dev/null)
82
+ if [ -n "$ROLE_PERM_FILES" ]; then
83
+ BAD_ROLE_REF=$(grep -Pn 'GenerateRoleGuid|GetAdminRoleId|GetManagerRoleId|GetViewerRoleId|GetContributorRoleId' $ROLE_PERM_FILES 2>/dev/null || true)
84
+ if [ -n "$BAD_ROLE_REF" ]; then
85
+ echo "WARNING: RolesSeedData uses hardcoded role GUID helpers instead of Code-based lookup"
86
+ echo "Fix: Use RoleCode string (e.g., 'admin') and resolve in SeedRolePermissionsAsync()"
87
+ echo "$BAD_ROLE_REF"
88
+ fi
89
+ fi
90
+
91
+ # POST-CHECK C16: Cross-tenant entities must use Guid? TenantId
92
+ for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev/null); do
93
+ if grep -q "IOptionalTenantEntity\|IScopedTenantEntity" "$entity"; then
94
+ if grep -q "public Guid TenantId" "$entity" && ! grep -q "public Guid? TenantId" "$entity"; then
95
+ echo "CRITICAL: Entity with IOptionalTenantEntity/IScopedTenantEntity must use Guid? TenantId (nullable)"
96
+ FAIL=true
97
+ fi
98
+ fi
99
+ done
100
+
101
+ # POST-CHECK C17: Scoped entities must have EntityScope property
102
+ for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev/null); do
103
+ if grep -q "IScopedTenantEntity" "$entity"; then
104
+ if ! grep -q "EntityScope\|Scope" "$entity"; then
105
+ echo "CRITICAL: Entity with IScopedTenantEntity must have EntityScope Scope property"
106
+ FAIL=true
107
+ fi
108
+ fi
109
+ done
110
+
111
+ # POST-CHECK C18: Permissions.cs static constants must exist (BLOCKING)
112
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
113
+ if [ -n "$CTRL_FILES" ]; then
114
+ PERM_REFS=$(grep -ohP 'Permissions\.\w+\.\w+' $CTRL_FILES 2>/dev/null | sed 's/Permissions\.\([^.]*\)\..*/\1/' | sort -u || true)
115
+ for MODULE in $PERM_REFS; do
116
+ PERM_FILE=$(find src/ -name "Permissions.cs" -exec grep -l "static class $MODULE" {} \; 2>/dev/null || true)
117
+ if [ -z "$PERM_FILE" ]; then
118
+ echo "BLOCKING: Controller references Permissions.${MODULE}.* but no Permissions.cs defines static class ${MODULE}"
119
+ echo "Fix: Create Application/Authorization/Permissions.cs with: public static class ${MODULE} { public const string Read = \"...\"; ... }"
120
+ FAIL=true
121
+ fi
122
+ done
123
+ fi
124
+
125
+ # POST-CHECK C19: ApplicationRolesSeedData.cs must exist (BLOCKING)
126
+ ROLE_SEED=$(find src/ -path "*/Seeding/Data/*" -name "*RolesSeedData.cs" 2>/dev/null | head -1)
127
+ if [ -n "$ROLE_SEED" ]; then
128
+ APP_ROLE_SEED=$(find src/ -path "*/Seeding/Data/ApplicationRolesSeedData.cs" 2>/dev/null | head -1)
129
+ if [ -z "$APP_ROLE_SEED" ]; then
130
+ echo "BLOCKING: RolesSeedData exists but ApplicationRolesSeedData.cs NOT FOUND"
131
+ echo "ApplicationRolesSeedData defines the 4 application-scoped roles (admin, manager, contributor, viewer)"
132
+ echo "Without it, SeedRolesAsync() has no role entries to create → RBAC broken"
133
+ echo "Fix: Create src/Infrastructure/Persistence/Seeding/Data/ApplicationRolesSeedData.cs"
134
+ FAIL=true
135
+ fi
136
+ fi
137
+
138
+ # POST-CHECK C20: Section route completeness (NavigationSection → frontend route + permissions)
139
+ SECTION_SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSectionSeedData.cs" 2>/dev/null)
140
+ APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
141
+ if [ -n "$SECTION_SEED_FILES" ] && [ -n "$APP_TSX" ]; then
142
+ SECTION_ROUTES=$(grep -Poh '"/[a-z][a-z0-9/-]+"' $SECTION_SEED_FILES 2>/dev/null | tr -d '"' | sort -u || true)
143
+ for SECTION_ROUTE in $SECTION_ROUTES; do
144
+ SECTION_SEG=$(echo "$SECTION_ROUTE" | rev | cut -d'/' -f1 | rev)
145
+ if ! grep -q "'$SECTION_SEG'" "$APP_TSX" && ! grep -q "\"$SECTION_SEG\"" "$APP_TSX"; then
146
+ echo "WARNING: NavigationSection seed data route has no matching frontend route: $SECTION_ROUTE"
147
+ echo "Expected path segment '$SECTION_SEG' in App.tsx application route block"
148
+ echo "Fix: Add section child routes to the module's children array in App.tsx"
149
+ fi
150
+ done
151
+ fi
152
+
153
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
154
+ if [ -n "$CTRL_FILES" ]; then
155
+ for f in $CTRL_FILES; do
156
+ SECTION_NAVROUTE=$(grep -oP 'NavRoute\("[a-z-]+\.[a-z-]+' "$f" 2>/dev/null)
157
+ if [ -n "$SECTION_NAVROUTE" ] && ! grep -q "\[RequirePermission" "$f"; then
158
+ echo "CRITICAL: Controller has [NavRoute] but no [RequirePermission]: $f"
159
+ echo "Fix: Add [RequirePermission(Permissions.{Section}.{Action})] on each endpoint"
160
+ FAIL=true
161
+ fi
162
+ done
163
+ fi
164
+
165
+ PERM_FILE=$(find src/ -name "Permissions.cs" -path "*/Authorization/*" 2>/dev/null | head -1)
166
+ if [ -n "$SECTION_SEED_FILES" ] && [ -n "$PERM_FILE" ]; then
167
+ SECTION_CODES=$(grep -oP 'Code\s*=\s*"([a-z]+)"' $SECTION_SEED_FILES 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' | sort -u || true)
168
+ for CODE in $SECTION_CODES; do
169
+ PASCAL=$(echo "$CODE" | sed 's/^./\U&/')
170
+ if ! grep -q "static class $PASCAL" "$PERM_FILE" 2>/dev/null; then
171
+ echo "WARNING: Section '$CODE' in seed data has no matching Permissions.$PASCAL static class"
172
+ echo "Fix: Add section-level permissions via MCP generate_permissions with 3-segment navRoute (app.module.section)"
173
+ fi
174
+ done
175
+ fi
176
+
177
+ # POST-CHECK C21: FORBIDDEN route patterns — /list and /detail/:id (WARNING)
178
+ SEED_NAV_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" -o -name "*NavigationSectionSeedData.cs" 2>/dev/null)
179
+ if [ -n "$SEED_NAV_FILES" ]; then
180
+ BAD_ROUTES=$(grep -Pn 'Route\s*=\s*.*"[^"]*/(list|detail)["/]' $SEED_NAV_FILES 2>/dev/null | grep -v '//.*Route' || true)
181
+ if [ -n "$BAD_ROUTES" ]; then
182
+ echo "WARNING: FORBIDDEN route pattern in seed data"
183
+ echo " - 'list' section route = module route (NO /list suffix)"
184
+ echo " - 'detail' section route = module route + /:id (NOT /detail/:id)"
185
+ echo "$BAD_ROUTES"
186
+ fi
187
+ fi
188
+
189
+ APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
190
+ if [ -n "$APP_TSX" ]; then
191
+ BAD_FE=$(grep -Pn "path:\s*['\"](?:list|detail)" "$APP_TSX" 2>/dev/null || true)
192
+ if [ -n "$BAD_FE" ]; then
193
+ echo "WARNING: FORBIDDEN frontend route path"
194
+ echo " - list = index: true (no 'list' path segment)"
195
+ echo " - detail = ':id' (no 'detail' path segment)"
196
+ echo "$BAD_FE"
197
+ fi
198
+ fi
199
+
200
+ # POST-CHECK C22: Permission path segment count (WARNING)
201
+ PERM_FILES=$(find src/ -path "*/Seeding/Data/*" -name "PermissionsSeedData.cs" 2>/dev/null)
202
+ if [ -n "$PERM_FILES" ]; then
203
+ while IFS= read -r line; do
204
+ PATH_VAL=$(echo "$line" | grep -oP '"[^"]*\.[^"]*"' | tr -d '"' || true)
205
+ if [ -n "$PATH_VAL" ]; then
206
+ DOTS=$(echo "$PATH_VAL" | tr -cd '.' | wc -c)
207
+ if echo "$PATH_VAL" | grep -qP '\.\*$'; then
208
+ continue
209
+ elif [ "$DOTS" -lt 2 ] || [ "$DOTS" -gt 4 ]; then
210
+ echo "WARNING: Permission path has unexpected segment count ($((DOTS+1)) segments): $PATH_VAL"
211
+ fi
212
+ fi
213
+ done < <(grep -n 'Path\s*=' $PERM_FILES 2>/dev/null)
214
+ fi
215
+
216
+ # POST-CHECK C23: IClientSeedDataProvider must have 4 methods + DI registration (BLOCKING)
217
+ PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
218
+ if [ -n "$PROVIDER" ]; then
219
+ METHODS_FOUND=0
220
+ for METHOD in SeedNavigationAsync SeedRolesAsync SeedPermissionsAsync SeedRolePermissionsAsync; do
221
+ if grep -q "$METHOD" "$PROVIDER"; then
222
+ METHODS_FOUND=$((METHODS_FOUND + 1))
223
+ else
224
+ echo "BLOCKING: IClientSeedDataProvider missing method: $METHOD in $PROVIDER"
225
+ FAIL=true
226
+ fi
227
+ done
228
+ if [ "$METHODS_FOUND" -lt 4 ]; then
229
+ echo "Fix: IClientSeedDataProvider must implement all 4 methods: SeedNavigationAsync, SeedRolesAsync, SeedPermissionsAsync, SeedRolePermissionsAsync"
230
+ fi
231
+
232
+ DI_FILE=$(find src/ -name "DependencyInjection.cs" -path "*/Infrastructure/*" 2>/dev/null | head -1)
233
+ if [ -n "$DI_FILE" ]; then
234
+ if ! grep -q "IClientSeedDataProvider" "$DI_FILE"; then
235
+ echo "BLOCKING: IClientSeedDataProvider not registered in DependencyInjection.cs"
236
+ echo "Fix: Add services.AddScoped<IClientSeedDataProvider, {App}SeedDataProvider>()"
237
+ FAIL=true
238
+ fi
239
+ fi
240
+ fi
241
+
242
+ # POST-CHECK C32: Translation seed data must have idempotency guard (CRITICAL)
243
+ PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
244
+ if [ -n "$PROVIDER" ]; then
245
+ TRANSLATION_ADDS=$(grep -c "NavigationTranslations.Add" "$PROVIDER" 2>/dev/null || true)
246
+ TRANSLATION_GUARDS=$(grep -c "NavigationTranslations.AnyAsync" "$PROVIDER" 2>/dev/null || true)
247
+
248
+ if [ "$TRANSLATION_ADDS" -gt 0 ] && [ "$TRANSLATION_GUARDS" -eq 0 ]; then
249
+ echo "CRITICAL: Translation seed data inserts without idempotency guard in $PROVIDER"
250
+ echo "Fix: Before each NavigationTranslations.Add block, check existence:"
251
+ echo " if (!await context.NavigationTranslations.AnyAsync("
252
+ echo " t => t.EntityId == {Module}NavigationSeedData.{Module}ModuleId"
253
+ echo " && t.EntityType == NavigationEntityType.Module, ct))"
254
+ echo " { foreach (var t in ...) { context.NavigationTranslations.Add(...); } }"
255
+ echo "The unique index IX_nav_Translations_EntityType_EntityId_LanguageCode will crash on duplicates."
256
+ FAIL=true
257
+ fi
258
+ fi
259
+
260
+ # POST-CHECK C33: Resource seed data must use actual section IDs from DB (CRITICAL)
261
+ PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
262
+ if [ -n "$PROVIDER" ]; then
263
+ if grep -Pn 'NavigationResource\.Create\(' "$PROVIDER" | grep -q 'resEntry\.SectionId\|secEntry\.Id'; then
264
+ echo "CRITICAL: Resource seed data uses seed-time GUID as SectionId in $PROVIDER"
265
+ echo "NavigationSection.Create() generates its own ID — seed-time GUIDs do NOT exist in nav_Sections."
266
+ echo "Fix: Query actual section from DB before creating resources:"
267
+ echo " var actualSection = await context.NavigationSections"
268
+ echo " .FirstAsync(s => s.Code == secEntry.Code && s.ModuleId == modEntity.Id, ct);"
269
+ echo " NavigationResource.Create(actualSection.Id, ...) // NOT secEntry.Id or resEntry.SectionId"
270
+ FAIL=true
271
+ fi
272
+ fi
273
+
274
+ # POST-CHECK C34: NavRoute segments must use kebab-case for multi-word codes (BLOCKING)
275
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
276
+ if [ -n "$CTRL_FILES" ]; then
277
+ for f in $CTRL_FILES; do
278
+ NAVROUTE_VAL=$(grep -oP 'NavRoute\("([^"]+)"\)' "$f" 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' || true)
279
+ if [ -n "$NAVROUTE_VAL" ]; then
280
+ for SEG in $(echo "$NAVROUTE_VAL" | tr '.' '\n'); do
281
+ if echo "$SEG" | grep -qP '^[a-z]{10,}$'; then
282
+ echo "BLOCKING: NavRoute segment '$SEG' in $f appears to be concatenated multi-word without hyphens"
283
+ echo " Full NavRoute: $NAVROUTE_VAL"
284
+ echo " Fix: Use kebab-case: e.g., 'humanresources' → 'human-resources'"
285
+ echo " SmartStack convention (from SmartStack.app): 'support-client.my-tickets'"
286
+ FAIL=true
287
+ fi
288
+ done
289
+ fi
290
+ done
291
+ fi
292
+
293
+ SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" -o -name "NavigationApplicationSeedData.cs" 2>/dev/null)
294
+ if [ -n "$SEED_FILES" ]; then
295
+ CODES=$(grep -oP 'Code\s*=\s*"([^"]+)"' $SEED_FILES 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' | sort -u || true)
296
+ for CODE in $CODES; do
297
+ if echo "$CODE" | grep -qP '^[a-z]{10,}$'; then
298
+ echo "BLOCKING: Navigation seed data Code '$CODE' appears to be concatenated multi-word without hyphens"
299
+ echo " Fix: Use kebab-case: e.g., 'humanresources' → 'human-resources'"
300
+ FAIL=true
301
+ fi
302
+ done
303
+ fi
304
+
305
+ # POST-CHECK C35: Permission codes must use kebab-case matching NavRoute codes (BLOCKING)
306
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
307
+ if [ -n "$CTRL_FILES" ]; then
308
+ for f in $CTRL_FILES; do
309
+ PERM_VALS=$(grep -oP 'RequirePermission\("([^"]+)"\)' "$f" 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' || true)
310
+ for PERM in $PERM_VALS; do
311
+ SEGMENTS=$(echo "$PERM" | tr '.' '\n' | head -n -1)
312
+ for SEG in $SEGMENTS; do
313
+ if echo "$SEG" | grep -qP '^[a-z]{10,}$'; then
314
+ echo "BLOCKING: Permission code segment '$SEG' in $f appears concatenated without hyphens"
315
+ echo " Full permission: $PERM"
316
+ echo " Fix: Use kebab-case: e.g., 'humanresources' → 'human-resources'"
317
+ echo " SmartStack convention: 'support-client.my-tickets.read'"
318
+ FAIL=true
319
+ fi
320
+ done
321
+ done
322
+ done
323
+ fi
324
+
325
+ PERM_FILES=$(find src/ -path "*/Authorization/Permissions.cs" 2>/dev/null)
326
+ if [ -n "$PERM_FILES" ]; then
327
+ for f in $PERM_FILES; do
328
+ CONST_VALS=$(grep -oP '=\s*"([^"]+)"' "$f" 2>/dev/null | grep -oP '"[^"]+"' | tr -d '"' || true)
329
+ for PERM in $CONST_VALS; do
330
+ SEGMENTS=$(echo "$PERM" | tr '.' '\n' | head -n -1)
331
+ for SEG in $SEGMENTS; do
332
+ if echo "$SEG" | grep -qP '^[a-z]{10,}$'; then
333
+ echo "BLOCKING: Permissions.cs constant segment '$SEG' in $f appears concatenated without hyphens"
334
+ echo " Full permission: $PERM"
335
+ echo " Fix: Use kebab-case: e.g., 'humanresources' → 'human-resources'"
336
+ FAIL=true
337
+ fi
338
+ done
339
+ done
340
+ done
341
+ fi
342
+
343
+ SEED_PERM_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*PermissionsSeedData.cs" 2>/dev/null)
344
+ if [ -n "$SEED_PERM_FILES" ]; then
345
+ PATHS=$(grep -oP '"[a-z][a-z0-9.-]+\.(read|create|update|delete|\*)"' $SEED_PERM_FILES 2>/dev/null | tr -d '"' || true)
346
+ for PERM in $PATHS; do
347
+ SEGMENTS=$(echo "$PERM" | tr '.' '\n' | head -n -1)
348
+ for SEG in $SEGMENTS; do
349
+ if echo "$SEG" | grep -qP '^[a-z]{10,}$'; then
350
+ echo "BLOCKING: PermissionsSeedData path segment '$SEG' appears concatenated without hyphens"
351
+ echo " Full permission path: $PERM"
352
+ echo " Fix: Use kebab-case matching NavRoute: 'humanresources' → 'human-resources'"
353
+ FAIL=true
354
+ fi
355
+ done
356
+ done
357
+ fi
358
+
359
+ # POST-CHECK C44: RolesSeedData must map standard role-permission matrix (CRITICAL)
360
+ ROLE_SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*RolesSeedData.cs" ! -name "ApplicationRolesSeedData.cs" 2>/dev/null)
361
+ if [ -n "$ROLE_SEED_FILES" ]; then
362
+ FAIL_C44=false
363
+ for f in $ROLE_SEED_FILES; do
364
+ BASENAME=$(basename "$f")
365
+ if [ "$BASENAME" = "ApplicationRolesSeedData.cs" ]; then continue; fi
366
+
367
+ HAS_ADMIN_WILDCARD=$(grep -Pc '(admin|Admin).*\*' "$f" 2>/dev/null || true)
368
+ if [ "$HAS_ADMIN_WILDCARD" -eq 0 ]; then
369
+ HAS_ADMIN_ACCESS=$(grep -Pc '(admin|Admin).*(Access|Wildcard|IsWildcard)' "$f" 2>/dev/null || true)
370
+ if [ "$HAS_ADMIN_ACCESS" -eq 0 ]; then
371
+ echo "CRITICAL: Admin role missing wildcard (*) permission in $f"
372
+ echo "Fix: Admin must map to wildcard permission (navRoute.*) or use IsWildcard=true"
373
+ FAIL_C44=true
374
+ fi
375
+ fi
376
+
377
+ VIEWER_WRITE=$(grep -Pc '(viewer|Viewer).*(\.delete|\.create|\.update|Delete|Create|Update)' "$f" 2>/dev/null || true)
378
+ if [ "$VIEWER_WRITE" -gt 0 ]; then
379
+ echo "CRITICAL: Viewer role has write permissions (create/update/delete) in $f"
380
+ echo "Fix: Viewer must only have read permission. Remove create/update/delete mappings."
381
+ FAIL_C44=true
382
+ fi
383
+
384
+ MANAGER_DELETE=$(grep -Pc '(manager|Manager).*(\.delete|Delete)' "$f" 2>/dev/null || true)
385
+ if [ "$MANAGER_DELETE" -gt 0 ]; then
386
+ echo "WARNING: Manager role has delete permission in $f"
387
+ echo "SmartStack standard: Manager = CRU (no delete). Verify this is intentional."
388
+ fi
389
+ done
390
+ if [ "$FAIL_C44" = true ]; then
391
+ FAIL=true
392
+ fi
393
+ fi
394
+
395
+ # POST-CHECK C45: PermissionAction enum must use valid typed values only (BLOCKING)
396
+ SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*.cs" 2>/dev/null)
397
+ if [ -n "$SEED_FILES" ]; then
398
+ FAIL_C45=false
399
+ for f in $SEED_FILES; do
400
+ ENUM_PARSE=$(grep -Pn 'Enum\.Parse<PermissionAction>' "$f" 2>/dev/null || true)
401
+ if [ -n "$ENUM_PARSE" ]; then
402
+ echo "BLOCKING: Enum.Parse<PermissionAction> detected — runtime crash risk: $f"
403
+ echo "$ENUM_PARSE"
404
+ echo "Fix: Use typed enum directly: PermissionAction.Read (NOT Enum.Parse<PermissionAction>(\"Read\"))"
405
+ FAIL_C45=true
406
+ fi
407
+
408
+ INVALID_CAST=$(grep -Pn '\(PermissionAction\)\s*([1-9]\d{1,}|[2-9]\d)' "$f" 2>/dev/null || true)
409
+ if [ -n "$INVALID_CAST" ]; then
410
+ echo "BLOCKING: Invalid PermissionAction cast detected (value > 10): $f"
411
+ echo "$INVALID_CAST"
412
+ echo "Valid values: Access(0), Read(1), Create(2), Update(3), Delete(4), Export(5), Import(6), Approve(7), Reject(8), Assign(9), Execute(10)"
413
+ FAIL_C45=true
414
+ fi
415
+ done
416
+ if [ "$FAIL_C45" = true ]; then
417
+ FAIL=true
418
+ fi
419
+ fi
420
+
421
+ # POST-CHECK C46: Navigation translation completeness — 4 languages per level (WARNING)
422
+ NAV_SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" ! -name "*Application*" 2>/dev/null)
423
+ if [ -n "$NAV_SEED_FILES" ]; then
424
+ FAIL_C46=false
425
+ for f in $NAV_SEED_FILES; do
426
+ HAS_FR=$(grep -c '"fr"' "$f" 2>/dev/null || true)
427
+ HAS_EN=$(grep -c '"en"' "$f" 2>/dev/null || true)
428
+ HAS_IT=$(grep -c '"it"' "$f" 2>/dev/null || true)
429
+ HAS_DE=$(grep -c '"de"' "$f" 2>/dev/null || true)
430
+
431
+ if [ "$HAS_FR" -eq 0 ] || [ "$HAS_EN" -eq 0 ] || [ "$HAS_IT" -eq 0 ] || [ "$HAS_DE" -eq 0 ]; then
432
+ echo "WARNING: Missing language(s) in navigation translations: $f"
433
+ echo " fr=$HAS_FR, en=$HAS_EN, it=$HAS_IT, de=$HAS_DE (all must be > 0)"
434
+ echo "Fix: Add NavigationTranslationSeedEntry for all 4 languages (fr, en, it, de)"
435
+ FAIL_C46=true
436
+ fi
437
+
438
+ HAS_SECTION_ENTRIES=$(grep -c 'GetSectionEntries' "$f" 2>/dev/null || true)
439
+ HAS_SECTION_TRANSLATIONS=$(grep -c 'GetSectionTranslationEntries' "$f" 2>/dev/null || true)
440
+ if [ "$HAS_SECTION_ENTRIES" -gt 0 ] && [ "$HAS_SECTION_TRANSLATIONS" -eq 0 ]; then
441
+ echo "WARNING: Sections defined but GetSectionTranslationEntries() missing: $f"
442
+ echo "Fix: Add GetSectionTranslationEntries() with 4 languages per section (ref core-seed-data.md §2b)"
443
+ FAIL_C46=true
444
+ fi
445
+
446
+ HAS_RESOURCE_ENTRIES=$(grep -c 'GetResourceEntries' "$f" 2>/dev/null || true)
447
+ HAS_RESOURCE_TRANSLATIONS=$(grep -Pc 'ResourceTranslation|GetResourceTranslation|NavigationEntityType\.Resource.*LanguageCode' "$f" 2>/dev/null || true)
448
+ if [ "$HAS_RESOURCE_ENTRIES" -gt 0 ] && [ "$HAS_RESOURCE_TRANSLATIONS" -eq 0 ]; then
449
+ echo "WARNING: Resources defined but resource translations missing: $f"
450
+ echo "Fix: Add resource translation entries with 4 languages per resource (ref core-seed-data.md §2b)"
451
+ FAIL_C46=true
452
+ fi
453
+ done
454
+ fi
455
+
456
+ # POST-CHECK C47: Person Extension entities must not duplicate User fields (WARNING)
457
+ ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
458
+ if [ -n "$ENTITY_FILES" ]; then
459
+ FAIL_C47=false
460
+ for f in $ENTITY_FILES; do
461
+ HAS_USERID=$(grep -P 'public\s+Guid\s+UserId\s*\{' "$f" 2>/dev/null || true)
462
+ if [ -z "$HAS_USERID" ]; then continue; fi
463
+
464
+ IS_MANDATORY=$(grep -P 'public\s+Guid\s+UserId\s*\{' "$f" 2>/dev/null | grep -v 'Guid?' || true)
465
+ if [ -z "$IS_MANDATORY" ]; then continue; fi
466
+
467
+ if ! grep -q "ITenantEntity" "$f"; then continue; fi
468
+
469
+ PERSON_FIELDS=$(grep -Pn 'public\s+string\S*\s+(FirstName|LastName|Email|PhoneNumber)\s*\{' "$f" 2>/dev/null || true)
470
+ if [ -n "$PERSON_FIELDS" ]; then
471
+ echo "WARNING: Mandatory person extension entity duplicates User fields: $f"
472
+ echo " Entity has non-nullable UserId (mandatory variant) — person fields come from User"
473
+ echo "$PERSON_FIELDS"
474
+ echo " Fix: Remove FirstName/LastName/Email/PhoneNumber — use Display* from User join in ResponseDto"
475
+ echo " See references/person-extension-pattern.md section 2"
476
+ FAIL_C47=true
477
+ fi
478
+ done
479
+ fi
480
+
481
+ # POST-CHECK C48: Person Extension service must Include(User) (CRITICAL)
482
+ ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
483
+ if [ -n "$ENTITY_FILES" ]; then
484
+ FAIL_C48=false
485
+ for f in $ENTITY_FILES; do
486
+ HAS_USERID=$(grep -P 'public\s+Guid\??\s+UserId\s*\{' "$f" 2>/dev/null || true)
487
+ if [ -z "$HAS_USERID" ]; then continue; fi
488
+ if ! grep -q "ITenantEntity" "$f"; then continue; fi
489
+
490
+ HAS_USER_NAV=$(grep -P 'public\s+User\?\s+User\s*\{' "$f" 2>/dev/null || true)
491
+ if [ -z "$HAS_USER_NAV" ]; then continue; fi
492
+
493
+ ENTITY_NAME=$(basename "$f" .cs)
494
+ SERVICE_FILE=$(find src/ -path "*/Services/*" -name "${ENTITY_NAME}Service.cs" ! -name "I${ENTITY_NAME}Service.cs" 2>/dev/null | head -1)
495
+ if [ -z "$SERVICE_FILE" ]; then continue; fi
496
+
497
+ HAS_INCLUDE=$(grep -P 'Include.*User' "$SERVICE_FILE" 2>/dev/null || true)
498
+ if [ -z "$HAS_INCLUDE" ]; then
499
+ echo "CRITICAL: Service for Person Extension entity must Include(x => x.User): $SERVICE_FILE"
500
+ echo " Entity: $f has UserId FK + User navigation property"
501
+ echo " Fix: Add .Include(x => x.User) to all queries in $SERVICE_FILE"
502
+ echo " Without Include, Display* fields will always be null"
503
+ echo " See references/person-extension-pattern.md section 5"
504
+ FAIL_C48=true
505
+ fi
506
+ done
507
+ if [ "$FAIL_C48" = true ]; then
508
+ FAIL=true
509
+ fi
510
+ fi
511
+
512
+ # POST-CHECK C53: Enum serialization — JsonStringEnumConverter required (BLOCKING)
513
+ PROGRAM_CS=$(find src/ -name "Program.cs" -path "*/Api/*" 2>/dev/null | head -1)
514
+ HAS_GLOBAL_CONVERTER=false
515
+ if [ -n "$PROGRAM_CS" ] && grep -q "JsonStringEnumConverter" "$PROGRAM_CS" 2>/dev/null; then
516
+ HAS_GLOBAL_CONVERTER=true
517
+ fi
518
+
519
+ if [ "$HAS_GLOBAL_CONVERTER" = false ]; then
520
+ ENUM_FILES=$(find src/ -path "*/Domain/Enums/*" -name "*.cs" 2>/dev/null)
521
+ if [ -n "$ENUM_FILES" ]; then
522
+ for f in $ENUM_FILES; do
523
+ if grep -q "public enum" "$f" && ! grep -q "JsonStringEnumConverter" "$f"; then
524
+ echo "BLOCKING: Enum missing [JsonConverter(typeof(JsonStringEnumConverter))]: $f"
525
+ echo " Frontend sends enum values as strings but C# deserializes as int by default"
526
+ echo " Fix: Add [JsonConverter(typeof(JsonStringEnumConverter))] on the enum"
527
+ echo " Or: Add JsonStringEnumConverter globally in Program.cs"
528
+ FAIL=true
529
+ fi
530
+ done
531
+ fi
532
+ fi
533
+
534
+ if [ "$FAIL" = true ]; then
535
+ exit 1
536
+ fi