@atlashub/smartstack-cli 3.31.0 → 3.32.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 (52) hide show
  1. package/.documentation/installation.html +7 -2
  2. package/README.md +7 -1
  3. package/dist/index.js +33 -37
  4. package/dist/index.js.map +1 -1
  5. package/dist/mcp-entry.mjs +547 -97
  6. package/dist/mcp-entry.mjs.map +1 -1
  7. package/package.json +1 -1
  8. package/scripts/health-check.sh +2 -1
  9. package/templates/mcp-scaffolding/controller.cs.hbs +10 -7
  10. package/templates/mcp-scaffolding/entity-extension.cs.hbs +132 -124
  11. package/templates/mcp-scaffolding/frontend/api-client.ts.hbs +4 -4
  12. package/templates/mcp-scaffolding/tests/controller.test.cs.hbs +38 -15
  13. package/templates/mcp-scaffolding/tests/service.test.cs.hbs +20 -8
  14. package/templates/skills/apex/SKILL.md +7 -9
  15. package/templates/skills/apex/_shared.md +9 -2
  16. package/templates/skills/apex/references/code-generation.md +412 -0
  17. package/templates/skills/apex/references/post-checks.md +377 -37
  18. package/templates/skills/apex/references/smartstack-api.md +229 -5
  19. package/templates/skills/apex/references/smartstack-frontend.md +368 -11
  20. package/templates/skills/apex/references/smartstack-layers.md +54 -7
  21. package/templates/skills/apex/steps/step-00-init.md +1 -2
  22. package/templates/skills/apex/steps/step-01-analyze.md +45 -2
  23. package/templates/skills/apex/steps/step-02-plan.md +23 -2
  24. package/templates/skills/apex/steps/step-03-execute.md +195 -5
  25. package/templates/skills/apex/steps/step-04-examine.md +18 -5
  26. package/templates/skills/apex/steps/step-05-deep-review.md +9 -11
  27. package/templates/skills/apex/steps/step-06-resolve.md +5 -9
  28. package/templates/skills/apex/steps/step-07-tests.md +66 -1
  29. package/templates/skills/apex/steps/step-08-run-tests.md +12 -3
  30. package/templates/skills/application/references/provider-template.md +62 -39
  31. package/templates/skills/application/templates-backend.md +3 -3
  32. package/templates/skills/application/templates-frontend.md +12 -12
  33. package/templates/skills/application/templates-seed.md +14 -4
  34. package/templates/skills/business-analyse/SKILL.md +9 -6
  35. package/templates/skills/business-analyse/questionnaire/04-data.md +8 -0
  36. package/templates/skills/business-analyse/references/agent-module-prompt.md +84 -5
  37. package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +83 -19
  38. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +6 -2
  39. package/templates/skills/business-analyse/references/team-orchestration.md +443 -110
  40. package/templates/skills/business-analyse/references/validation-checklist.md +5 -4
  41. package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +44 -0
  42. package/templates/skills/business-analyse/steps/step-03a2-analysis.md +72 -1
  43. package/templates/skills/business-analyse/steps/step-03c-compile.md +93 -7
  44. package/templates/skills/business-analyse/steps/step-03d-validate.md +34 -2
  45. package/templates/skills/business-analyse/steps/step-04b-analyze.md +40 -0
  46. package/templates/skills/controller/references/controller-code-templates.md +2 -2
  47. package/templates/skills/controller/templates.md +12 -12
  48. package/templates/skills/feature-full/steps/step-01-implementation.md +4 -4
  49. package/templates/skills/ralph-loop/references/category-rules.md +44 -2
  50. package/templates/skills/ralph-loop/references/compact-loop.md +37 -0
  51. package/templates/skills/ralph-loop/references/core-seed-data.md +51 -20
  52. package/templates/skills/review-code/references/owasp-api-top10.md +1 -1
@@ -28,13 +28,22 @@ SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Se
28
28
  if [ -n "$SERVICE_FILES" ]; then
29
29
  # Check each service file has TenantId reference (either _currentUser.TenantId or TenantId filter)
30
30
  for f in $SERVICE_FILES; do
31
- if ! grep -q "TenantId" "$f"; then
32
- echo "BLOCKING (OWASP A01): Service missing TenantId filter: $f"
33
- echo "Every service query MUST filter by _currentUser.TenantId"
31
+ # Accept either _currentTenant.TenantId (strict guard clause or nullable usage)
32
+ # or entities with IOptionalTenantEntity/IScopedTenantEntity (optional tenant pattern)
33
+ HAS_TENANT_FILTER=$(grep -c "TenantId" "$f")
34
+ HAS_OPTIONAL_ENTITY=false
35
+ if grep -q "IOptionalTenantEntity\|IScopedTenantEntity" "$f"; then
36
+ HAS_OPTIONAL_ENTITY=true
37
+ fi
38
+
39
+ if [ "$HAS_TENANT_FILTER" -eq 0 ] && [ "$HAS_OPTIONAL_ENTITY" = false ]; then
40
+ echo "BLOCKING (OWASP A01): Service missing TenantId filter or optional tenant entity: $f"
41
+ echo "Every service query MUST filter by _currentTenant.TenantId"
42
+ echo "OR work with entities that implement IOptionalTenantEntity/IScopedTenantEntity"
34
43
  exit 1
35
44
  fi
36
45
  if grep -q "Guid.Empty" "$f"; then
37
- echo "BLOCKING (OWASP A01): Service uses Guid.Empty instead of _currentUser.TenantId: $f"
46
+ echo "BLOCKING (OWASP A01): Service uses Guid.Empty instead of _currentTenant.TenantId: $f"
38
47
  exit 1
39
48
  fi
40
49
  done
@@ -123,19 +132,35 @@ if [ -n "$ROUTE_FILES" ]; then
123
132
  fi
124
133
  ```
125
134
 
126
- ### POST-CHECK 8: Forms must be full pages with routes — ZERO modals/popups/drawers
135
+ ### POST-CHECK 8: Forms must be full pages with routes — ZERO modals/popups/drawers/slide-overs
127
136
 
128
137
  ```bash
129
- # Check for modal/dialog/drawer imports in page files (create/edit forms)
138
+ # Check for modal/dialog/drawer/slide-over imports AND inline form state in page files
130
139
  PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null)
131
140
  if [ -n "$PAGE_FILES" ]; then
132
- MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet)" $PAGE_FILES 2>/dev/null)
141
+ FAIL=false
142
+
143
+ # 8a. Component imports (Modal, Dialog, Drawer, etc.)
144
+ MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet|SlideOver|Overlay)" $PAGE_FILES 2>/dev/null)
133
145
  if [ -n "$MODAL_IMPORTS" ]; then
134
- echo "BLOCKING: Form pages must NOT use Modal/Dialog/Drawer/Popup components"
135
- echo "Create/Edit forms MUST be full pages with their own URL routes"
136
- echo "Found modal imports in page files:"
146
+ echo "BLOCKING: Form pages must NOT use Modal/Dialog/Drawer/Popup/SlideOver components"
137
147
  echo "$MODAL_IMPORTS"
148
+ FAIL=true
149
+ fi
150
+
151
+ # 8b. Inline form state (catches drawers/slide-overs built without external components)
152
+ FORM_STATE=$(grep -Pn "useState.*(?:isOpen|showModal|showDialog|showCreate|showEdit|showForm|isCreating|isEditing|showDrawer|showPanel|showSlideOver|selectedEntity|editingEntity)" $PAGE_FILES 2>/dev/null)
153
+ if [ -n "$FORM_STATE" ]; then
154
+ echo "BLOCKING: Inline form state detected — forms embedded in ListPage as drawers/panels"
155
+ echo "Create/Edit forms MUST be separate page components with their own URL routes"
156
+ echo "$FORM_STATE"
157
+ FAIL=true
158
+ fi
159
+
160
+ if [ "$FAIL" = true ]; then
161
+ echo ""
138
162
  echo "Fix: Create EntityCreatePage.tsx with route /create and EntityEditPage.tsx with route /:id/edit"
163
+ echo "NEVER embed create/edit forms as inline drawers, panels, or slide-overs in ListPage"
139
164
  exit 1
140
165
  fi
141
166
  fi
@@ -193,31 +218,64 @@ if [ -n "$FORM_PAGES" ]; then
193
218
  fi
194
219
  ```
195
220
 
196
- ### POST-CHECK 11: FK fields must NOT be plain text inputs (EntityLookup required)
221
+ ### POST-CHECK 11: FK fields must use EntityLookup NO `<input>`, NO `<select>` (BLOCKING)
197
222
 
198
223
  ```bash
199
- # Check for FK fields rendered as plain text inputs in form pages
200
- FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test | grep -v node_modules)
201
- if [ -n "$FORM_PAGES" ]; then
202
- # Detect FK input fields: <input> with name ending in "Id" (catches inputs with or without explicit type)
203
- FK_INPUTS=$(grep -Pn '<input[^>]*name=["\x27][a-zA-Z]*Id["\x27]' $FORM_PAGES 2>/dev/null)
224
+ # Check ALL page files for FK fields rendered as <input> or <select> instead of EntityLookup
225
+ # Scans ALL .tsx files (not just CreatePage/EditPage forms may be embedded in ListPage drawers)
226
+ ALL_PAGES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\.")
227
+ if [ -n "$ALL_PAGES" ]; then
228
+ FAIL=false
229
+
230
+ # 1. Detect <input> with name/value binding to FK fields (fields ending in "Id")
231
+ FK_INPUTS=$(grep -Pn '<input[^>]*(?:name|value)=["\x27{][^>]*[a-zA-Z]Id["\x27}]' $ALL_PAGES 2>/dev/null | grep -Pv 'type=["\x27]hidden["\x27]')
204
232
  if [ -n "$FK_INPUTS" ]; then
205
- # Filter out hidden inputs (legitimate for FK submission)
206
- FK_TEXT_INPUTS=$(echo "$FK_INPUTS" | grep -Pv 'type=["\x27]hidden["\x27]')
207
- if [ -n "$FK_TEXT_INPUTS" ]; then
208
- echo "BLOCKING: FK fields rendered as plain text inputs — MUST use EntityLookup component"
209
- echo "Users cannot type GUIDs manually. Use <EntityLookup /> from @/components/ui/EntityLookup"
210
- echo "See smartstack-frontend.md section 6 for the EntityLookup pattern"
211
- echo "$FK_TEXT_INPUTS"
212
- exit 1
213
- fi
233
+ echo "BLOCKING: FK fields rendered as <input> MUST use EntityLookup component"
234
+ echo "$FK_INPUTS"
235
+ FAIL=true
214
236
  fi
215
237
 
216
- # Check for input placeholders mentioning "ID" or "id" or "Enter...Id"
217
- FK_PLACEHOLDER=$(grep -Pn 'placeholder=["\x27].*[Ee]nter.*[Ii][Dd]|placeholder=["\x27].*[Gg][Uu][Ii][Dd]' $FORM_PAGES 2>/dev/null)
238
+ # 2. Detect <select> with value binding to FK fields (e.g., value={formData.departmentId})
239
+ FK_SELECTS=$(grep -Pn '<select[^>]*value=\{[^}]*[a-zA-Z]Id\b' $ALL_PAGES 2>/dev/null)
240
+ if [ -n "$FK_SELECTS" ]; then
241
+ echo "BLOCKING: FK fields rendered as <select> dropdown — MUST use EntityLookup component"
242
+ echo "A <select> loaded from API state is NOT a valid substitute for EntityLookup."
243
+ echo "EntityLookup provides: debounced search, paginated results, display name resolution."
244
+ echo "$FK_SELECTS"
245
+ FAIL=true
246
+ fi
247
+
248
+ # 3. Detect onChange handlers setting FK fields from <select> (e.g., setFormData({...formData, departmentId: e.target.value}))
249
+ FK_SELECT_ONCHANGE=$(grep -Pn 'onChange=.*[a-zA-Z]Id[^a-zA-Z].*e\.target\.value' $ALL_PAGES 2>/dev/null)
250
+ if [ -n "$FK_SELECT_ONCHANGE" ]; then
251
+ echo "BLOCKING: FK field set via e.target.value (select/input pattern) — use EntityLookup onChange(id)"
252
+ echo "$FK_SELECT_ONCHANGE"
253
+ FAIL=true
254
+ fi
255
+
256
+ # 4. Check for placeholders mentioning "ID", "GUID", or "Select..." for FK fields
257
+ FK_PLACEHOLDER=$(grep -Pn 'placeholder=["\x27].*(?:[Ee]nter.*[Ii][Dd]|[Gg][Uu][Ii][Dd]|[Ss]elect.*[Ee]mployee|[Ss]elect.*[Dd]epartment|[Ss]elect.*[Pp]osition|[Ss]elect.*[Pp]roject|[Ss]elect.*[Cc]ategory)' $ALL_PAGES 2>/dev/null)
218
258
  if [ -n "$FK_PLACEHOLDER" ]; then
219
- echo "BLOCKING: Form has placeholder asking user to enter an ID/GUID — use EntityLookup instead"
259
+ echo "BLOCKING: Form has placeholder for FK field selection — use EntityLookup search instead"
220
260
  echo "$FK_PLACEHOLDER"
261
+ FAIL=true
262
+ fi
263
+
264
+ # 5. Detect <option> elements with GUID-like values (sign of FK <select>)
265
+ FK_OPTIONS=$(grep -Pn '<option[^>]*value=\{[^}]*\.id\}' $ALL_PAGES 2>/dev/null)
266
+ if [ -n "$FK_OPTIONS" ]; then
267
+ echo "BLOCKING: <option> with entity .id value detected — this is a FK <select> anti-pattern"
268
+ echo "Replace the entire <select>/<option> block with <EntityLookup />"
269
+ echo "$FK_OPTIONS"
270
+ FAIL=true
271
+ fi
272
+
273
+ if [ "$FAIL" = true ]; then
274
+ echo ""
275
+ echo "Fix: Replace ALL FK fields with <EntityLookup /> from @/components/ui/EntityLookup"
276
+ echo "See smartstack-frontend.md section 6 for the EntityLookup pattern"
277
+ echo "FORBIDDEN for FK Guid fields: <input>, <select>, <option>, e.target.value"
278
+ echo "REQUIRED: <EntityLookup apiEndpoint={...} mapOption={...} value={...} onChange={...} />"
221
279
  exit 1
222
280
  fi
223
281
  fi
@@ -242,7 +300,7 @@ if [ -n "$CTRL_FILES" ]; then
242
300
  fi
243
301
  ```
244
302
 
245
- ### POST-CHECK 13: No hardcoded Tailwind colors in generated pages
303
+ ### POST-CHECK 13: No hardcoded Tailwind colors in generated pages (BLOCKING)
246
304
 
247
305
  ```bash
248
306
  # Scan all page and component files directly (works for uncommitted/untracked files, Windows/WSL compatible)
@@ -250,9 +308,24 @@ ALL_PAGES=$(find src/pages/ src/components/ -name "*.tsx" 2>/dev/null | grep -v
250
308
  if [ -n "$ALL_PAGES" ]; then
251
309
  HARDCODED=$(grep -Pn '(bg|text|border)-(?!\[)(red|blue|green|gray|white|black|slate|zinc|neutral|stone)-' $ALL_PAGES 2>/dev/null)
252
310
  if [ -n "$HARDCODED" ]; then
253
- echo "WARNING: Pages should use CSS variables instead of hardcoded Tailwind colors"
254
- echo "Fix: bg-[var(--bg-card)] instead of bg-white, text-[var(--text-primary)] instead of text-gray-900"
311
+ echo "BLOCKING: Pages MUST use CSS variables instead of hardcoded Tailwind colors"
312
+ echo "SmartStack uses a theme system hardcoded colors break dark mode and custom themes"
313
+ echo ""
314
+ echo "Fix mapping:"
315
+ echo " bg-white → bg-[var(--bg-card)]"
316
+ echo " bg-gray-50 → bg-[var(--bg-primary)]"
317
+ echo " text-gray-900 → text-[var(--text-primary)]"
318
+ echo " text-gray-500 → text-[var(--text-secondary)]"
319
+ echo " border-gray-200 → border-[var(--border-color)]"
320
+ echo " bg-blue-600 → bg-[var(--color-accent-500)]"
321
+ echo " text-blue-600 → text-[var(--color-accent-500)]"
322
+ echo " text-red-500 → text-[var(--error-text)]"
323
+ echo " bg-green-500 → bg-[var(--success-bg)]"
324
+ echo ""
325
+ echo "See references/smartstack-frontend.md section 4 for full variable reference"
326
+ echo ""
255
327
  echo "$HARDCODED"
328
+ exit 1
256
329
  fi
257
330
  fi
258
331
  ```
@@ -509,7 +582,35 @@ if [ -n "$SERVICE_FILES" ]; then
509
582
  fi
510
583
  ```
511
584
 
512
- ### POST-CHECK 22: Permissions.cs static constants must exist (BLOCKING)
585
+ ### POST-CHECK 22: Cross-tenant entities must use Guid? TenantId
586
+
587
+ ```bash
588
+ for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev/null); do
589
+ if grep -q "IOptionalTenantEntity\|IScopedTenantEntity" "$entity"; then
590
+ if grep -q "public Guid TenantId" "$entity" && ! grep -q "public Guid? TenantId" "$entity"; then
591
+ echo "BLOCKING: Entity with IOptionalTenantEntity/IScopedTenantEntity must use Guid? TenantId (nullable)"
592
+ exit 1
593
+ fi
594
+ fi
595
+ done
596
+ echo "POST-CHECK 22: OK"
597
+ ```
598
+
599
+ ### POST-CHECK 23: Scoped entities must have EntityScope property
600
+
601
+ ```bash
602
+ for entity in $(find src/ -path "*/Domain/*" -name "*.cs" ! -name "I*.cs" 2>/dev/null); do
603
+ if grep -q "IScopedTenantEntity" "$entity"; then
604
+ if ! grep -q "EntityScope\|Scope" "$entity"; then
605
+ echo "BLOCKING: Entity with IScopedTenantEntity must have EntityScope Scope property"
606
+ exit 1
607
+ fi
608
+ fi
609
+ done
610
+ echo "POST-CHECK 23: OK"
611
+ ```
612
+
613
+ ### POST-CHECK 24: Permissions.cs static constants must exist (BLOCKING)
513
614
 
514
615
  ```bash
515
616
  # Every module with controllers MUST have a Permissions.cs with static constants
@@ -527,7 +628,7 @@ if [ -n "$CTRL_FILES" ]; then
527
628
  fi
528
629
  ```
529
630
 
530
- ### POST-CHECK 23: ApplicationRolesSeedData.cs must exist (BLOCKING)
631
+ ### POST-CHECK 25: ApplicationRolesSeedData.cs must exist (BLOCKING)
531
632
 
532
633
  ```bash
533
634
  # If any RolesSeedData exists, ApplicationRolesSeedData MUST also exist
@@ -544,7 +645,7 @@ if [ -n "$ROLE_SEED" ]; then
544
645
  fi
545
646
  ```
546
647
 
547
- ### POST-CHECK 24b: Section route completeness (NavigationSection → frontend route + permissions)
648
+ ### POST-CHECK 25b: Section route completeness (NavigationSection → frontend route + permissions)
548
649
 
549
650
  ```bash
550
651
  # Every NavigationSection seed data route MUST have a corresponding frontend route in App.tsx
@@ -593,7 +694,7 @@ if [ -n "$SECTION_SEED_FILES" ] && [ -n "$PERM_FILE" ]; then
593
694
  fi
594
695
  ```
595
696
 
596
- ### POST-CHECK 25: FORBIDDEN route patterns — /list and /detail/:id (BLOCKING)
697
+ ### POST-CHECK 26: FORBIDDEN route patterns — /list and /detail/:id (BLOCKING)
597
698
 
598
699
  ```bash
599
700
  # 1. Check seed data for FORBIDDEN suffixes
@@ -624,7 +725,7 @@ fi
624
725
  echo "OK: No forbidden /list or /detail route patterns found"
625
726
  ```
626
727
 
627
- ### POST-CHECK 26: Permission path segment count (WARNING)
728
+ ### POST-CHECK 27: Permission path segment count (WARNING)
628
729
 
629
730
  ```bash
630
731
  PERM_FILES=$(find src/ -path "*/Seeding/Data/*" -name "PermissionsSeedData.cs" 2>/dev/null)
@@ -646,7 +747,7 @@ if [ -n "$PERM_FILES" ]; then
646
747
  fi
647
748
  ```
648
749
 
649
- ### POST-CHECK 24: IClientSeedDataProvider must have 4 methods + DI registration (BLOCKING)
750
+ ### POST-CHECK 28: IClientSeedDataProvider must have 4 methods + DI registration (BLOCKING)
650
751
 
651
752
  ```bash
652
753
  PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
@@ -676,4 +777,243 @@ if [ -n "$PROVIDER" ]; then
676
777
  fi
677
778
  ```
678
779
 
780
+ ### POST-CHECK 29: i18n must use separate JSON files per language (not embedded in index.ts)
781
+
782
+ ```bash
783
+ # Translations MUST be in src/i18n/locales/{lang}/{module}.json, NOT embedded in a single .ts file
784
+ TSX_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\.")
785
+ if [ -n "$TSX_FILES" ]; then
786
+ # Check if i18n locales directory exists
787
+ if [ ! -d "src/i18n/locales" ]; then
788
+ echo "BLOCKING: Missing src/i18n/locales/ directory"
789
+ echo "Translations must be in separate JSON files: src/i18n/locales/{fr,en,it,de}/{module}.json"
790
+ echo "NEVER embed translations in src/i18n/index.ts or a single TypeScript file"
791
+ exit 1
792
+ fi
793
+
794
+ # Check for embedded translations in index.ts (common anti-pattern)
795
+ I18N_INDEX=$(find src/i18n/ -maxdepth 1 -name "index.ts" -o -name "index.tsx" -o -name "config.ts" 2>/dev/null)
796
+ if [ -n "$I18N_INDEX" ]; then
797
+ EMBEDDED=$(grep -Pn '^\s*(resources|translations)\s*[:=]\s*\{' $I18N_INDEX 2>/dev/null)
798
+ if [ -n "$EMBEDDED" ]; then
799
+ echo "BLOCKING: Translations embedded in i18n config file — must be in separate JSON files"
800
+ echo "Found embedded translations in:"
801
+ echo "$EMBEDDED"
802
+ echo ""
803
+ echo "Fix: Move translations to src/i18n/locales/{fr,en,it,de}/{module}.json"
804
+ echo "The i18n config should import from locales/ directory, not contain inline translations"
805
+ exit 1
806
+ fi
807
+ fi
808
+
809
+ # Verify all 4 language directories exist
810
+ for LANG in fr en it de; do
811
+ if [ ! -d "src/i18n/locales/$LANG" ]; then
812
+ echo "BLOCKING: Missing language directory: src/i18n/locales/$LANG/"
813
+ echo "SmartStack requires 4 languages: fr, en, it, de"
814
+ exit 1
815
+ fi
816
+ done
817
+
818
+ # Verify at least one JSON file exists per language
819
+ for LANG in fr en it de; do
820
+ JSON_COUNT=$(find "src/i18n/locales/$LANG" -name "*.json" 2>/dev/null | wc -l)
821
+ if [ "$JSON_COUNT" -eq 0 ]; then
822
+ echo "BLOCKING: No translation JSON files in src/i18n/locales/$LANG/"
823
+ echo "Each module must have a {module}.json file per language"
824
+ exit 1
825
+ fi
826
+ done
827
+ fi
828
+ ```
829
+
830
+ ### POST-CHECK 30: Pages must use useTranslation hook (no hardcoded user-facing strings)
831
+
832
+ ```bash
833
+ # Verify that page components use i18n — detect hardcoded strings in JSX
834
+ PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\.")
835
+ if [ -n "$PAGE_FILES" ]; then
836
+ # Check that at least 80% of pages import useTranslation
837
+ TOTAL_PAGES=$(echo "$PAGE_FILES" | wc -l)
838
+ I18N_PAGES=$(grep -l "useTranslation" $PAGE_FILES 2>/dev/null | wc -l)
839
+ if [ "$TOTAL_PAGES" -gt 0 ] && [ "$I18N_PAGES" -eq 0 ]; then
840
+ echo "BLOCKING: No page files use useTranslation — all user-facing text must be translated"
841
+ echo "Found $TOTAL_PAGES page files but 0 use useTranslation"
842
+ echo ""
843
+ echo "Fix: Import and use useTranslation in every page component:"
844
+ echo " const { t } = useTranslation(['{module}']);"
845
+ echo " t('{module}:title', 'Fallback text')"
846
+ exit 1
847
+ fi
848
+
849
+ # Check for common hardcoded English strings in JSX (heuristic)
850
+ HARDCODED_TEXT=$(grep -Pn '>\s*(Create|Edit|Delete|Save|Cancel|Search|Loading|Error|No data|Not found|Submit|Back|Actions|Name|Status|Description)\s*<' $PAGE_FILES 2>/dev/null | grep -v '{t(' | head -10)
851
+ if [ -n "$HARDCODED_TEXT" ]; then
852
+ echo "WARNING: Possible hardcoded user-facing strings detected in JSX"
853
+ echo "All user-facing text MUST use t('namespace:key', 'Fallback')"
854
+ echo "$HARDCODED_TEXT"
855
+ fi
856
+ fi
857
+ ```
858
+
859
+ ### POST-CHECK 31: List/Detail pages must include DocToggleButton (documentation panel)
860
+
861
+ ```bash
862
+ # Every list and detail page MUST have DocToggleButton for inline documentation access
863
+ LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\.")
864
+ if [ -n "$LIST_PAGES" ]; then
865
+ MISSING_DOC=0
866
+ for PAGE in $LIST_PAGES; do
867
+ if ! grep -q "DocToggleButton" "$PAGE" 2>/dev/null; then
868
+ echo "WARNING: Page missing DocToggleButton: $PAGE"
869
+ echo " Import: import { DocToggleButton } from '@/components/docs/DocToggleButton';"
870
+ echo " Place in header actions: <DocToggleButton />"
871
+ MISSING_DOC=$((MISSING_DOC + 1))
872
+ fi
873
+ done
874
+ if [ "$MISSING_DOC" -gt 0 ]; then
875
+ echo ""
876
+ echo "WARNING: $MISSING_DOC pages missing DocToggleButton — users cannot access inline documentation"
877
+ echo "See smartstack-frontend.md section 7 for placement pattern"
878
+ fi
879
+ fi
880
+ ```
881
+
882
+ ### POST-CHECK 32: Module documentation must be generated (doc-data.ts)
883
+
884
+ ```bash
885
+ # After frontend pages exist, /documentation should have been called
886
+ TSX_PAGES=$(find src/pages/ -name "*.tsx" -not -name "*.test.*" 2>/dev/null | grep -v node_modules | grep -v "docs/")
887
+ DOC_DATA=$(find src/pages/docs/ -name "doc-data.ts" 2>/dev/null)
888
+ if [ -n "$TSX_PAGES" ] && [ -z "$DOC_DATA" ]; then
889
+ echo "WARNING: Frontend pages exist but no documentation generated"
890
+ echo "Fix: Invoke /documentation {module} --type user to generate doc-data.ts"
891
+ echo "The DocToggleButton in page headers will link to this documentation"
892
+ fi
893
+ ```
894
+
895
+ ### POST-CHECK 33: Pagination type must be PaginatedResult<T> — no aliases (BLOCKING)
896
+
897
+ ```bash
898
+ SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
899
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
900
+ ALL_FILES="$SERVICE_FILES $CTRL_FILES"
901
+ if [ -n "$(echo $ALL_FILES | tr -d ' ')" ]; then
902
+ BAD_NAMES=$(grep -Pn 'PagedResult<|PaginatedResultDto<|PaginatedResponse<|PageResultDto<' $ALL_FILES 2>/dev/null)
903
+ if [ -n "$BAD_NAMES" ]; then
904
+ echo "BLOCKING: Pagination type must be PaginatedResult<T> — found non-canonical names"
905
+ echo "$BAD_NAMES"
906
+ echo "FORBIDDEN type names: PagedResult, PaginatedResultDto, PaginatedResponse, PageResultDto"
907
+ echo "Fix: Use PaginatedResult<T> from SmartStack.Application.Common.Models everywhere"
908
+ exit 1
909
+ fi
910
+ fi
911
+ ```
912
+
913
+ ### POST-CHECK 34: Code generation — ICodeGenerator must be registered for auto-generated entities (BLOCKING)
914
+
915
+ ```bash
916
+ # If feature.json has entities with codePattern.strategy != "manual",
917
+ # verify that ICodeGenerator<Entity> is registered in DI
918
+ FEATURE_FILES=$(find docs/ -name "feature.json" 2>/dev/null)
919
+ DI_FILE=$(find src/ -name "DependencyInjection.cs" -path "*/Infrastructure/*" 2>/dev/null | head -1)
920
+ if [ -n "$FEATURE_FILES" ] && [ -n "$DI_FILE" ]; then
921
+ for FEATURE in $FEATURE_FILES; do
922
+ ENTITIES_WITH_CODE=$(python3 -c "
923
+ import json, sys
924
+ try:
925
+ with open('$FEATURE') as f:
926
+ data = json.load(f)
927
+ for e in data.get('analysis', {}).get('entities', []):
928
+ cp = e.get('codePattern', {})
929
+ if cp.get('strategy', 'manual') != 'manual':
930
+ print(e['name'])
931
+ except: pass
932
+ " 2>/dev/null)
933
+ for ENTITY in $ENTITIES_WITH_CODE; do
934
+ if ! grep -q "ICodeGenerator<$ENTITY>" "$DI_FILE" 2>/dev/null; then
935
+ echo "BLOCKING: Entity $ENTITY has auto-generated code pattern but ICodeGenerator<$ENTITY> is not registered in DI"
936
+ echo "Fix: Add CodeGenerator<$ENTITY> registration in DependencyInjection.cs — see references/code-generation.md"
937
+ exit 1
938
+ fi
939
+ done
940
+ done
941
+ fi
942
+ ```
943
+
944
+ ### POST-CHECK 35: Code regex must support hyphens (BLOCKING)
945
+
946
+ ```bash
947
+ VALIDATOR_FILES=$(find src/ -path "*/Validators/*" -name "*Validator.cs" 2>/dev/null)
948
+ if [ -n "$VALIDATOR_FILES" ]; then
949
+ OLD_REGEX=$(grep -rn '\^\\[a-z0-9_\\]+\$' $VALIDATOR_FILES 2>/dev/null | grep -v '\-')
950
+ if [ -n "$OLD_REGEX" ]; then
951
+ echo "BLOCKING: Code validator uses old regex without hyphen support"
952
+ echo "$OLD_REGEX"
953
+ echo "Fix: Update regex to ^[a-z0-9_-]+$ to support auto-generated codes with hyphens"
954
+ exit 1
955
+ fi
956
+ fi
957
+ ```
958
+
959
+ ### POST-CHECK 36: CreateDto must NOT have Code field when service uses ICodeGenerator (WARNING)
960
+
961
+ ```bash
962
+ SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
963
+ if [ -n "$SERVICE_FILES" ]; then
964
+ for f in $SERVICE_FILES; do
965
+ if grep -q "ICodeGenerator" "$f"; then
966
+ ENTITY=$(basename "$f" | sed 's/Service\.cs$//')
967
+ DTO_FILE=$(find src/ -path "*/DTOs/*" -name "Create${ENTITY}Dto.cs" 2>/dev/null | head -1)
968
+ if [ -n "$DTO_FILE" ] && grep -q "public string Code" "$DTO_FILE"; then
969
+ echo "WARNING: Create${ENTITY}Dto has Code field but service uses ICodeGenerator (code is auto-generated)"
970
+ echo "Fix: Remove Code from Create${ENTITY}Dto — it is auto-generated by ICodeGenerator<${ENTITY}>"
971
+ fi
972
+ fi
973
+ done
974
+ fi
975
+ ```
976
+
977
+ ### POST-CHECK 37: Translation seed data must have idempotency guard (BLOCKING)
978
+
979
+ ```bash
980
+ PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
981
+ if [ -n "$PROVIDER" ]; then
982
+ # Check if NavigationTranslations.Add is used WITHOUT a preceding AnyAsync guard
983
+ # Pattern: any .Add(NavigationTranslation.Create(...)) that is NOT inside an AnyAsync check
984
+ TRANSLATION_ADDS=$(grep -c "NavigationTranslations.Add" "$PROVIDER" 2>/dev/null)
985
+ TRANSLATION_GUARDS=$(grep -c "NavigationTranslations.AnyAsync" "$PROVIDER" 2>/dev/null)
986
+
987
+ if [ "$TRANSLATION_ADDS" -gt 0 ] && [ "$TRANSLATION_GUARDS" -eq 0 ]; then
988
+ echo "BLOCKING: Translation seed data inserts without idempotency guard in $PROVIDER"
989
+ echo "Fix: Before each NavigationTranslations.Add block, check existence:"
990
+ echo " if (!await context.NavigationTranslations.AnyAsync("
991
+ echo " t => t.EntityId == {Module}NavigationSeedData.{Module}ModuleId"
992
+ echo " && t.EntityType == NavigationEntityType.Module, ct))"
993
+ echo " { foreach (var t in ...) { context.NavigationTranslations.Add(...); } }"
994
+ echo "The unique index IX_nav_Translations_EntityType_EntityId_LanguageCode will crash on duplicates."
995
+ exit 1
996
+ fi
997
+ fi
998
+ ```
999
+
1000
+ ### POST-CHECK 38: Resource seed data must use actual section IDs from DB (BLOCKING)
1001
+
1002
+ ```bash
1003
+ PROVIDER=$(find src/ -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
1004
+ if [ -n "$PROVIDER" ]; then
1005
+ # Check if NavigationResource.Create uses secEntry.Id or resEntry.SectionId (deterministic GUIDs)
1006
+ # instead of actualSection.Id (real DB ID). This causes FK_nav_Resources_nav_Sections_SectionId violation.
1007
+ if grep -Pn 'NavigationResource\.Create\(' "$PROVIDER" | grep -q 'resEntry\.SectionId\|secEntry\.Id'; then
1008
+ echo "BLOCKING: Resource seed data uses deterministic GUID as SectionId in $PROVIDER"
1009
+ echo "NavigationSection.Create() generates its own ID — deterministic seed GUIDs do NOT exist in nav_Sections."
1010
+ echo "Fix: Query actual section from DB before creating resources:"
1011
+ echo " var actualSection = await context.NavigationSections"
1012
+ echo " .FirstAsync(s => s.Code == secEntry.Code && s.ModuleId == modEntity.Id, ct);"
1013
+ echo " NavigationResource.Create(actualSection.Id, ...) // NOT secEntry.Id or resEntry.SectionId"
1014
+ exit 1
1015
+ fi
1016
+ fi
1017
+ ```
1018
+
679
1019
  **If ANY POST-CHECK fails → fix in step-03, re-validate.**