@atlashub/smartstack-cli 3.27.0 → 3.29.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/dist/index.js +6 -7
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -3
  4. package/templates/project/api.ts.template +4 -2
  5. package/templates/project/appsettings.json.template +1 -1
  6. package/templates/skills/apex/_shared.md +13 -0
  7. package/templates/skills/apex/references/post-checks.md +228 -6
  8. package/templates/skills/apex/references/smartstack-api.md +67 -17
  9. package/templates/skills/apex/references/smartstack-frontend.md +41 -1
  10. package/templates/skills/apex/references/smartstack-layers.md +40 -10
  11. package/templates/skills/apex/steps/step-02-plan.md +16 -11
  12. package/templates/skills/apex/steps/step-03-execute.md +6 -0
  13. package/templates/skills/apex/steps/step-04-examine.md +4 -2
  14. package/templates/skills/application/references/frontend-verification.md +26 -1
  15. package/templates/skills/application/steps/step-03-roles.md +1 -1
  16. package/templates/skills/application/steps/step-05-frontend.md +24 -8
  17. package/templates/skills/application/templates-frontend.md +41 -22
  18. package/templates/skills/application/templates-seed.md +53 -16
  19. package/templates/skills/business-analyse/SKILL.md +4 -2
  20. package/templates/skills/business-analyse/_shared.md +17 -4
  21. package/templates/skills/business-analyse/react/schema.md +1 -1
  22. package/templates/skills/business-analyse/references/agent-module-prompt.md +11 -9
  23. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +4 -3
  24. package/templates/skills/business-analyse/references/deploy-modes.md +1 -1
  25. package/templates/skills/business-analyse/references/handoff-file-templates.md +4 -4
  26. package/templates/skills/business-analyse/references/robustness-checks.md +12 -9
  27. package/templates/skills/business-analyse/references/spec-auto-inference.md +3 -3
  28. package/templates/skills/business-analyse/references/ui-resource-cards.md +3 -3
  29. package/templates/skills/business-analyse/references/validation-checklist.md +21 -3
  30. package/templates/skills/business-analyse/schemas/sections/specification-schema.json +33 -5
  31. package/templates/skills/business-analyse/steps/step-03b-ui.md +2 -2
  32. package/templates/skills/business-analyse/steps/step-03c-compile.md +17 -9
  33. package/templates/skills/business-analyse/steps/step-03d-validate.md +1 -1
  34. package/templates/skills/business-analyse/steps/step-04b-analyze.md +5 -3
  35. package/templates/skills/business-analyse/steps/step-05a-handoff.md +24 -16
  36. package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +2 -2
  37. package/templates/skills/business-analyse/templates/tpl-handoff.md +10 -8
  38. package/templates/skills/business-analyse/templates/tpl-progress.md +7 -6
  39. package/templates/skills/ralph-loop/references/category-rules.md +104 -9
  40. package/templates/skills/ralph-loop/references/compact-loop.md +124 -3
  41. package/templates/skills/ralph-loop/references/core-seed-data.md +158 -38
  42. package/templates/skills/ralph-loop/references/task-transform-legacy.md +3 -3
  43. package/templates/skills/ralph-loop/steps/step-02-execute.md +311 -15
  44. package/templates/skills/ralph-loop/steps/step-03-commit.md +2 -2
  45. package/templates/skills/ralph-loop/steps/step-04-check.md +4 -3
  46. package/templates/skills/ralph-loop/steps/step-05-report.md +51 -1
@@ -99,6 +99,20 @@ if [ -n "$UPPERCASE_ROUTES" ]; then
99
99
  echo "Fix: Use ToKebabCase() helper in route generation (see core-seed-data.md)"
100
100
  exit 1
101
101
  fi
102
+
103
+ # Search for multi-word route segments missing hyphens (e.g., "humanresources" instead of "human-resources")
104
+ MULTI_WORD_ROUTES=$(grep -oP 'Route\s*=\s*"([^"]+)"' Infrastructure/Persistence/Seeding/Data/*/NavigationSeedData.cs 2>/dev/null | grep -P '"[^"]*(?:human(?:resources|resource)|time(?:management|tracking)|project(?:management|member)|leave(?:balance|request))[^"]*"')
105
+
106
+ if [ -n "$MULTI_WORD_ROUTES" ]; then
107
+ echo "❌ BLOCKING: Route segments contain concatenated multi-word names without hyphens"
108
+ echo "Found: $MULTI_WORD_ROUTES"
109
+ echo ""
110
+ echo "Convention: multi-word segments MUST use kebab-case"
111
+ echo " humanresources → human-resources"
112
+ echo " timemanagement → time-management"
113
+ echo "Fix: Apply ToKebabCase() to all route segments in NavigationSeedData"
114
+ exit 1
115
+ fi
102
116
  ```
103
117
 
104
118
  **Why this matters:**
@@ -164,6 +178,102 @@ fi
164
178
  - If `auth_Permissions` is empty → all authorization checks reject → 403 on every endpoint
165
179
  - These are **foundational** — without them, ALL subsequent features (frontend, API, tests) are useless
166
180
 
181
+ **POST-CHECK: Section route/permission completeness (BLOCKING — when sections defined)**
182
+
183
+ After generating section seed data, verify completeness:
184
+
185
+ ```bash
186
+ # 1. Verify every NavigationSection seed route has a frontend route match
187
+ SECTION_SEED_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" -exec grep -l 'GetSectionEntries' {} \; 2>/dev/null)
188
+ if [ -n "$SECTION_SEED_FILES" ]; then
189
+ echo "Sections defined — verifying frontend route matches..."
190
+ SECTION_ROUTES=$(grep -ohP 'Route\s*=\s*ToKebabCase\(\$?"([^"]+)"\)' $SECTION_SEED_FILES 2>/dev/null | grep -i section)
191
+ APP_TSX="$WEB_SRC/App.tsx"
192
+ if [ -n "$APP_TSX" ] && [ -n "$SECTION_ROUTES" ]; then
193
+ while IFS= read -r ROUTE; do
194
+ ROUTE_PATH=$(echo "$ROUTE" | grep -oP '"([^"]+)"' | tr -d '"')
195
+ if [ -n "$ROUTE_PATH" ] && ! grep -q "$ROUTE_PATH" "$APP_TSX" 2>/dev/null; then
196
+ echo "WARNING: Section route '$ROUTE_PATH' not found in App.tsx — add frontend route"
197
+ fi
198
+ done <<< "$SECTION_ROUTES"
199
+ fi
200
+
201
+ # 2. Verify section-level permissions exist when sections are defined
202
+ PERM_SEED=$(find . -path "*/Seeding/Data/*" -name "PermissionsSeedData.cs" 2>/dev/null)
203
+ if [ -n "$PERM_SEED" ]; then
204
+ for PS in $PERM_SEED; do
205
+ HAS_SECTION_PERMS=$(grep -c 'sectionCode\|SectionPascal\|Level = PermissionLevel.Section' "$PS" 2>/dev/null)
206
+ if [ "$HAS_SECTION_PERMS" -eq 0 ]; then
207
+ echo "BLOCKING: Sections defined but PermissionsSeedData missing section-level permissions: $PS"
208
+ echo "Fix: Add section-level permission GUIDs and entries per core-seed-data.md Step C2"
209
+ exit 1
210
+ fi
211
+ done
212
+ fi
213
+
214
+ # 3. Verify section-level role mappings exist
215
+ ROLE_SEED=$(find . -path "*/Seeding/Data/*" -name "RolesSeedData.cs" 2>/dev/null)
216
+ if [ -n "$ROLE_SEED" ]; then
217
+ for RS in $ROLE_SEED; do
218
+ HAS_SECTION_ROLES=$(grep -c 'sectionCode' "$RS" 2>/dev/null)
219
+ if [ "$HAS_SECTION_ROLES" -eq 0 ]; then
220
+ echo "BLOCKING: Sections defined but RolesSeedData missing section-level role mappings: $RS"
221
+ echo "Fix: Add section-level role-permission entries per core-seed-data.md section 5"
222
+ exit 1
223
+ fi
224
+ done
225
+ fi
226
+ fi
227
+ ```
228
+
229
+ **POST-CHECK: Navigation section route CRUD suffix detection (BLOCKING)**
230
+
231
+ After generating NavigationSeedData files with sections, verify no route contains `/list` or `/detail` suffixes:
232
+
233
+ ```bash
234
+ # Detect CRUD suffixes in navigation section routes
235
+ CRUD_ROUTES=$(grep -rn 'Route.*=.*\/list\b\|Route.*=.*\/detail' Infrastructure/Persistence/Seeding/Data/ 2>/dev/null | grep -v '//.*Route')
236
+
237
+ if [ -n "$CRUD_ROUTES" ]; then
238
+ echo "BLOCKING: Navigation section routes contain CRUD suffixes"
239
+ echo "Convention:"
240
+ echo " - 'list' section route = module route (NO /list suffix)"
241
+ echo " - 'detail' section route = module route + /:id (NOT /detail/:id)"
242
+ echo " - Other sections = module route + /{section-kebab}"
243
+ echo ""
244
+ echo "Found:"
245
+ echo "$CRUD_ROUTES"
246
+ echo ""
247
+ echo "Fix: Remove /list suffix (use module route), replace /detail/:id with /:id"
248
+ exit 1
249
+ fi
250
+ ```
251
+
252
+ **Why this matters:**
253
+ - React Router defines NO `/list` child route — the module route IS the list view
254
+ - React Router uses `/:id` for detail views, NOT `/detail/:id`
255
+ - Mismatch causes 404 on navigation menu click
256
+
257
+ **POST-CHECK: Permission path segment count (WARNING — when permissions defined)**
258
+
259
+ ```bash
260
+ PERM_FILES=$(find Infrastructure/Persistence/Seeding/Data/ -name "PermissionsSeedData.cs" 2>/dev/null)
261
+ if [ -n "$PERM_FILES" ]; then
262
+ MALFORMED=$(grep -oP 'Path\s*=\s*"([^"]+)"' $PERM_FILES | grep -oP '"[^"]+"' | tr -d '"' | while read -r path; do
263
+ DOTS=$(echo "$path" | tr -cd '.' | wc -c)
264
+ if echo "$path" | grep -qP '\.\*$'; then continue; fi
265
+ if [ "$DOTS" -lt 3 ] || [ "$DOTS" -gt 5 ]; then
266
+ echo " Unexpected segment count ($((DOTS+1))): $path"
267
+ fi
268
+ done)
269
+ if [ -n "$MALFORMED" ]; then
270
+ echo "WARNING: Permission paths with unexpected segment count:"
271
+ echo "$MALFORMED"
272
+ echo " Expected: 4 segments (module-level) or 5 segments (section-level)"
273
+ fi
274
+ fi
275
+ ```
276
+
167
277
  **POST-CHECK: DefaultTenantId cross-schema FK validation (WARNING)**
168
278
 
169
279
  After generating SeedConstants.cs and DevDataSeeder, verify that `DefaultTenantId` is not a phantom GUID that doesn't exist in `core.tenant_Tenants`:
@@ -244,9 +354,22 @@ npm run lint # ESLint — MUST exit 0
244
354
  If FAIL → read errors → fix TSX/TS files → re-run → loop until pass.
245
355
 
246
356
  **Frontend POST-CHECKs (BLOCKING for frontend category):**
357
+
358
+ > **Path resolution:** Web projects may live in subdirectories (e.g., `web/app-web/src/pages/`).
359
+ > All POST-CHECKs below use dynamic path discovery instead of hardcoded `src/pages/`.
360
+
361
+ ```bash
362
+ # Resolve frontend directories dynamically (handles web/ prefix and varied project structures)
363
+ PAGE_DIR=$(find . -path "*/src/pages" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
364
+ HOOK_DIR=$(find . -path "*/src/hooks" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
365
+ COMP_DIR=$(find . -path "*/src/components" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
366
+ WEB_SRC=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
367
+ # All POST-CHECKs below use $PAGE_DIR, $HOOK_DIR, $COMP_DIR, $WEB_SRC instead of hardcoded paths
368
+ ```
369
+
247
370
  ```bash
248
371
  # POST-CHECK: Forms must be full pages — ZERO modals/popups/drawers
249
- PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null)
372
+ PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" 2>/dev/null)
250
373
  if [ -n "$PAGE_FILES" ]; then
251
374
  MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet)" $PAGE_FILES 2>/dev/null)
252
375
  if [ -n "$MODAL_IMPORTS" ]; then
@@ -257,8 +380,92 @@ if [ -n "$PAGE_FILES" ]; then
257
380
  fi
258
381
  fi
259
382
 
383
+ # POST-CHECK: No raw HTML <table> in page files — MUST use SmartTable/DataTable (BLOCKING)
384
+ PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" 2>/dev/null)
385
+ if [ -n "$PAGE_FILES" ]; then
386
+ RAW_TABLES=$(grep -Pn '<table[\s>]|<thead[\s>]|<tbody[\s>]' $PAGE_FILES 2>/dev/null)
387
+ if [ -n "$RAW_TABLES" ]; then
388
+ echo "BLOCKING: Raw HTML <table> detected in page files — MUST use SmartTable or DataTable"
389
+ echo "SmartStack convention: Lists MUST use SmartTable + SmartFilter (see /ui-components skill)"
390
+ echo "Import: import { DataTable } from '@/components/ui/DataTable'"
391
+ echo ""
392
+ echo "Found:"
393
+ echo "$RAW_TABLES"
394
+ exit 1
395
+ fi
396
+ fi
397
+
398
+ # POST-CHECK: No hardcoded Tailwind colors — MUST use CSS variables (BLOCKING)
399
+ PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" -o -name "*.tsx" 2>/dev/null | sort -u)
400
+ COMPONENT_FILES=$(find "$COMP_DIR" -name "*.tsx" 2>/dev/null)
401
+ ALL_TSX="$PAGE_FILES $COMPONENT_FILES"
402
+ if [ -n "$ALL_TSX" ]; then
403
+ # Match bg-{color}-{shade} or text-{color}-{shade} patterns (excluding bg-white, bg-black, bg-transparent, bg-current)
404
+ HARDCODED_COLORS=$(grep -Pn '(?:bg|text|border|ring|from|to|via)-(?:red|blue|green|yellow|orange|purple|pink|indigo|teal|cyan|emerald|violet|fuchsia|rose|amber|lime|sky|slate|gray|zinc|neutral|stone)-\d{2,3}' $ALL_TSX 2>/dev/null | head -20)
405
+ if [ -n "$HARDCODED_COLORS" ]; then
406
+ echo "BLOCKING: Hardcoded Tailwind colors detected — MUST use CSS variables"
407
+ echo "SmartStack convention: ALL colors via CSS variables for theming support"
408
+ echo " bg-blue-600 → bg-[var(--color-accent-600)]"
409
+ echo " text-red-500 → text-[var(--error-text)]"
410
+ echo " bg-green-100 → bg-[var(--success-bg)]"
411
+ echo " bg-gray-50 → bg-[var(--bg-secondary)]"
412
+ echo ""
413
+ echo "Found:"
414
+ echo "$HARDCODED_COLORS"
415
+ exit 1
416
+ fi
417
+ fi
418
+
419
+ # POST-CHECK: List pages must import SmartTable/DataTable OR EntityCard (WARNING)
420
+ LIST_PAGES=$(find "$PAGE_DIR" -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
421
+ if [ -n "$LIST_PAGES" ]; then
422
+ for LP in $LIST_PAGES; do
423
+ HAS_TABLE=$(grep -c 'SmartTable\|DataTable' "$LP" 2>/dev/null)
424
+ HAS_CARD=$(grep -c 'EntityCard' "$LP" 2>/dev/null)
425
+ if [ "$HAS_TABLE" -eq 0 ] && [ "$HAS_CARD" -eq 0 ]; then
426
+ echo "WARNING: List page missing SmartTable/DataTable or EntityCard: $LP"
427
+ echo "List pages MUST use SmartStack UI components from /ui-components skill"
428
+ echo " Tables: import { DataTable } from '@/components/ui/DataTable'"
429
+ echo " Cards: import { EntityCard } from '@/components/ui/EntityCard'"
430
+ fi
431
+ done
432
+ fi
433
+
434
+ # POST-CHECK: No window.confirm() — MUST use confirmation component (BLOCKING)
435
+ PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" 2>/dev/null)
436
+ if [ -n "$PAGE_FILES" ]; then
437
+ BAD_CONFIRM=$(grep -Pn 'window\.confirm\s*\(|(?<!\w)confirm\s*\(' $PAGE_FILES 2>/dev/null | grep -v '//' | grep -v test)
438
+ if [ -n "$BAD_CONFIRM" ]; then
439
+ echo "BLOCKING: window.confirm() detected — MUST use a SmartStack confirmation component"
440
+ echo "Native browser dialogs break theming and i18n"
441
+ echo "Use a ConfirmDialog component with translated messages"
442
+ echo ""
443
+ echo "Found:"
444
+ echo "$BAD_CONFIRM"
445
+ exit 1
446
+ fi
447
+ fi
448
+
449
+ # POST-CHECK: Pages must handle loading, error, and empty states (WARNING)
450
+ PAGE_FILES=$(find "$PAGE_DIR" -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
451
+ if [ -n "$PAGE_FILES" ]; then
452
+ for LP in $PAGE_FILES; do
453
+ HAS_LOADING=$(grep -c 'loading\|isLoading\|skeleton\|Skeleton\|PageLoader\|spinner' "$LP" 2>/dev/null)
454
+ HAS_ERROR=$(grep -c 'error\|Error' "$LP" 2>/dev/null)
455
+ HAS_EMPTY=$(grep -c 'empty\|Empty\|no.*found\|length === 0' "$LP" 2>/dev/null)
456
+ MISSING=""
457
+ [ "$HAS_LOADING" -eq 0 ] && MISSING="${MISSING}loading "
458
+ [ "$HAS_ERROR" -eq 0 ] && MISSING="${MISSING}error "
459
+ [ "$HAS_EMPTY" -eq 0 ] && MISSING="${MISSING}empty "
460
+ if [ -n "$MISSING" ]; then
461
+ echo "WARNING: List page missing state handling ($MISSING): $LP"
462
+ echo "All list pages MUST handle: loading skeleton, error with retry, empty state"
463
+ fi
464
+ done
465
+ fi
466
+
260
467
  # POST-CHECK: Create/Edit pages must exist for each module with a list page
261
- LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
468
+ LIST_PAGES=$(find "$PAGE_DIR" -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
262
469
  if [ -n "$LIST_PAGES" ]; then
263
470
  for LP in $LIST_PAGES; do
264
471
  DIR=$(dirname "$LP"); MOD=$(basename "$DIR")
@@ -272,9 +479,9 @@ if [ -n "$LIST_PAGES" ]; then
272
479
  fi
273
480
 
274
481
  # First check: at least one test file must exist in pages/
275
- PAGE_FILES=$(find src/pages/ -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null)
482
+ PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null)
276
483
  if [ -n "$PAGE_FILES" ]; then
277
- TEST_FILES=$(find src/pages/ -name "*.test.tsx" 2>/dev/null)
484
+ TEST_FILES=$(find "$PAGE_DIR" -name "*.test.tsx" 2>/dev/null)
278
485
  if [ -z "$TEST_FILES" ]; then
279
486
  echo "BLOCKING: No frontend test files found in src/pages/"
280
487
  echo "Every module with pages MUST have at least one .test.tsx file"
@@ -283,7 +490,7 @@ if [ -n "$PAGE_FILES" ]; then
283
490
  fi
284
491
 
285
492
  # POST-CHECK: Form pages must have companion test files
286
- FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test)
493
+ FORM_PAGES=$(find "$PAGE_DIR" -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test)
287
494
  if [ -n "$FORM_PAGES" ]; then
288
495
  for FP in $FORM_PAGES; do
289
496
  TEST="${FP%.tsx}.test.tsx"
@@ -295,7 +502,7 @@ if [ -n "$FORM_PAGES" ]; then
295
502
  fi
296
503
 
297
504
  # POST-CHECK: FK fields must NOT be plain text inputs — use EntityLookup
298
- FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test | grep -v node_modules)
505
+ FORM_PAGES=$(find "$PAGE_DIR" -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test | grep -v node_modules)
299
506
  if [ -n "$FORM_PAGES" ]; then
300
507
  # Match any input with name ending in "Id" (except hidden inputs)
301
508
  FK_TEXT_INPUTS=$(grep -Pn '<input[^>]*name=["\x27][a-zA-Z]*Id["\x27]' $FORM_PAGES 2>/dev/null | grep -v 'type=["\x27]hidden["\x27]')
@@ -326,15 +533,34 @@ if [ -n "$CTRL_FILES" ]; then
326
533
  done
327
534
  fi
328
535
 
329
- # POST-CHECK: Route seed data vs frontend cross-validation
536
+ # POST-CHECK: Route seed data vs frontend cross-validation (CONVENTION + EXISTENCE)
330
537
  SEED_NAV_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" 2>/dev/null)
331
- APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
538
+ APP_TSX="$WEB_SRC/App.tsx"
332
539
  if [ -n "$SEED_NAV_FILES" ] && [ -n "$APP_TSX" ]; then
333
540
  SEED_ROUTES=$(grep -ohP 'Route\s*=\s*"([^"]+)"' $SEED_NAV_FILES | sed 's/Route\s*=\s*"//' | sed 's/"//' | sort -u)
334
541
  CLIENT_PATHS=$(grep -ohP "path:\s*['\"]([^'\"]+)['\"]" "$APP_TSX" | sed "s/path:\s*['\"]//;s/['\"]//" | sort -u)
335
542
  if [ -n "$SEED_ROUTES" ] && [ -z "$CLIENT_PATHS" ]; then
336
543
  echo "WARNING: Seed data has navigation routes but App.tsx has no client routes defined"
337
544
  fi
545
+ # Cross-check: frontend paths must use same kebab-case segments as backend API routes
546
+ API_CONTROLLERS=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
547
+ if [ -n "$API_CONTROLLERS" ] && [ -n "$CLIENT_PATHS" ]; then
548
+ API_SEGMENTS=$(grep -ohP '\[Route\("api/([^"]+)"\)\]' $API_CONTROLLERS | sed 's/\[Route("api\///;s/")\]//' | tr '/' '\n' | sort -u)
549
+ for SEG in $API_SEGMENTS; do
550
+ # Check if frontend path contains same segment (kebab-case match)
551
+ if echo "$SEG" | grep -qP '[a-z]+-[a-z]+'; then
552
+ # Multi-word segment like "human-resources" — verify frontend uses same convention
553
+ NO_HYPHEN=$(echo "$SEG" | tr -d '-')
554
+ if echo "$CLIENT_PATHS" | grep -q "$NO_HYPHEN"; then
555
+ echo "BLOCKING: Frontend route uses '$NO_HYPHEN' but backend API uses '$SEG' (kebab-case)"
556
+ echo "Fix: Frontend paths MUST match backend route convention"
557
+ echo " Backend: api/business/$SEG/..."
558
+ echo " Frontend: /business/$SEG/... (NOT /business/$NO_HYPHEN/...)"
559
+ exit 1
560
+ fi
561
+ fi
562
+ done
563
+ fi
338
564
  fi
339
565
 
340
566
  # POST-CHECK: HasQueryFilter anti-pattern
@@ -359,20 +585,78 @@ if [ -n "$SERVICE_FILES" ]; then
359
585
  fi
360
586
  fi
361
587
 
362
- # POST-CHECK: i18n required key structure
363
- FR_I18N_FILES=$(find src/i18n/locales/fr/ -name "*.json" 2>/dev/null)
364
- if [ -n "$FR_I18N_FILES" ]; then
365
- for f in $FR_I18N_FILES; do
588
+ # POST-CHECK: i18n file structure — per-module JSON files MUST exist (BLOCKING)
589
+ WEB_DIR=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
590
+ if [ -n "$WEB_DIR" ]; then
591
+ I18N_DIR="$WEB_DIR/i18n/locales"
592
+ if [ -d "$I18N_DIR" ]; then
593
+ for LANG in fr en it de; do
594
+ LANG_DIR="$I18N_DIR/$LANG"
595
+ if [ ! -d "$LANG_DIR" ]; then
596
+ echo "BLOCKING: Missing i18n locale directory: $LANG_DIR"
597
+ echo "All 4 languages (fr, en, it, de) MUST have a dedicated directory"
598
+ exit 1
599
+ fi
600
+ # Check for at least one module-specific JSON (not just common.json)
601
+ MODULE_JSONS=$(find "$LANG_DIR" -name "*.json" ! -name "common.json" ! -name "navigation.json" 2>/dev/null)
602
+ if [ -z "$MODULE_JSONS" ]; then
603
+ echo "BLOCKING: No module translation files in $LANG_DIR/"
604
+ echo "Each module MUST have src/i18n/locales/$LANG/{moduleLower}.json"
605
+ exit 1
606
+ fi
607
+ done
608
+ else
609
+ echo "BLOCKING: Missing i18n/locales directory — i18n file structure not created"
610
+ echo "Expected: src/i18n/locales/{fr,en,it,de}/{module}.json"
611
+ exit 1
612
+ fi
613
+ fi
614
+
615
+ # POST-CHECK: i18n required key structure (BLOCKING — upgraded from WARNING)
616
+ if [ -d "$I18N_DIR/fr" ]; then
617
+ for f in $(find "$I18N_DIR/fr/" -name "*.json" 2>/dev/null); do
366
618
  BASENAME=$(basename "$f")
367
619
  case "$BASENAME" in common.json|navigation.json) continue;; esac
368
- for KEY in "actions" "labels" "errors"; do
620
+ for KEY in "actions" "labels" "errors" "messages" "empty"; do
369
621
  if ! grep -q "\"$KEY\"" "$f"; then
370
- echo "WARNING: i18n file missing required key '$KEY': $f"
622
+ echo "BLOCKING: i18n file missing required key group '$KEY': $f"
623
+ echo "All module i18n files MUST contain: actions, labels, errors, messages, empty"
624
+ exit 1
371
625
  fi
372
626
  done
373
627
  done
374
628
  fi
375
629
 
630
+ # POST-CHECK: Pages must NOT contain hardcoded English UI text (BLOCKING)
631
+ PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null)
632
+ if [ -n "$PAGE_FILES" ]; then
633
+ # Detect common hardcoded English patterns in JSX (excluding imports/comments/code)
634
+ HARDCODED_TEXT=$(grep -Pn '>\s*(Create|Edit|Delete|Save|Cancel|Search|Loading|Error|No .* found|Are you sure|Submit|Back|Actions|Status|Name|Description|Total)\s*<' $PAGE_FILES 2>/dev/null | grep -v '//' | grep -v 'test' | head -20)
635
+ if [ -n "$HARDCODED_TEXT" ]; then
636
+ echo "BLOCKING: Hardcoded English UI text found in page files"
637
+ echo "ALL visible text MUST use t() from useTranslation() — NEVER hardcode strings"
638
+ echo "Pattern: t('{module}:actions.create', 'Create') — namespace + fallback"
639
+ echo ""
640
+ echo "Found:"
641
+ echo "$HARDCODED_TEXT"
642
+ exit 1
643
+ fi
644
+ fi
645
+
646
+ # POST-CHECK: Hooks must NOT contain hardcoded error messages (BLOCKING)
647
+ HOOK_FILES=$(find "$HOOK_DIR" -name "*.ts" -o -name "*.tsx" 2>/dev/null)
648
+ if [ -n "$HOOK_FILES" ]; then
649
+ HARDCODED_ERRORS=$(grep -Pn "(setError|throw new Error)\(['\"](?!t\()[A-Z][a-z]+" $HOOK_FILES 2>/dev/null | head -15)
650
+ if [ -n "$HARDCODED_ERRORS" ]; then
651
+ echo "BLOCKING: Hardcoded error messages in hooks — MUST use i18n t() function"
652
+ echo "Hooks must import useTranslation and use t('{module}:errors.loadFailed') for all messages"
653
+ echo ""
654
+ echo "Found:"
655
+ echo "$HARDCODED_ERRORS"
656
+ exit 1
657
+ fi
658
+ fi
659
+
376
660
  # POST-CHECK: SeedConstants must NOT contain ContextId (pre-seeded by SmartStack core)
377
661
  SEED_CONST_FILES=$(find src/ -path "*/Seeding/*" -name "SeedConstants.cs" 2>/dev/null)
378
662
  if [ -n "$SEED_CONST_FILES" ]; then
@@ -414,12 +698,24 @@ if [ -n "$SERVICE_FILES" ]; then
414
698
  BAD_PATTERN=$(grep -Pn 'TenantId!\s*\.Value|TenantId!\s*\.ToString|\.TenantId!' $SERVICE_FILES 2>/dev/null)
415
699
  if [ -n "$BAD_PATTERN" ]; then
416
700
  echo "BLOCKING: TenantId!.Value causes 500 when tenant context is missing"
417
- echo "Fix: var tenantId = _currentTenant.TenantId ?? throw new UnauthorizedAccessException(\"Tenant context is required\");"
701
+ echo "Fix: var tenantId = _currentTenant.TenantId ?? throw new TenantContextRequiredException();"
702
+ echo "NEVER use UnauthorizedAccessException for tenant context — it returns 401 which clears the frontend token."
418
703
  echo "$BAD_PATTERN"
419
704
  exit 1
420
705
  fi
421
706
  fi
422
707
 
708
+ # POST-CHECK: Services must NOT use UnauthorizedAccessException for tenant context (causes token clearing)
709
+ if [ -n "$SERVICE_FILES" ]; then
710
+ BAD_UNAUTH=$(grep -Pn 'UnauthorizedAccessException.*[Tt]enant' $SERVICE_FILES 2>/dev/null)
711
+ if [ -n "$BAD_UNAUTH" ]; then
712
+ echo "BLOCKING: Services use UnauthorizedAccessException for tenant context — causes 401 which clears the frontend token"
713
+ echo "$BAD_UNAUTH"
714
+ echo "Fix: var tenantId = _currentTenant.TenantId ?? throw new TenantContextRequiredException();"
715
+ exit 1
716
+ fi
717
+ fi
718
+
423
719
  # POST-CHECK: IAuditableEntity + Validator pairing
424
720
  ENTITY_FILES=$(find src/ -path "*/Domain/Entities/Business/*" -name "*.cs" 2>/dev/null)
425
721
  if [ -n "$ENTITY_FILES" ]; then
@@ -24,9 +24,9 @@ if [ "{current_task_category}" != "frontend" ] && [ "{current_task_category}" !=
24
24
  dotnet build --no-restore --verbosity quiet
25
25
  fi
26
26
 
27
- # Frontend: typecheck must pass
27
+ # Frontend: typecheck + lint must pass
28
28
  if [ "{current_task_category}" = "frontend" ]; then
29
- npm run typecheck
29
+ npm run typecheck && npm run lint
30
30
  fi
31
31
 
32
32
  # Tests: full suite if test project exists
@@ -80,7 +80,7 @@ fi
80
80
 
81
81
  ```javascript
82
82
  const presentCategories = new Set(prd.tasks.map(t => t.category));
83
- const REQUIRED_CATEGORIES = ['domain', 'infrastructure', 'application', 'api', 'frontend', 'test'];
83
+ const REQUIRED_CATEGORIES = ['domain', 'infrastructure', 'application', 'api', 'seedData', 'frontend', 'test'];
84
84
  const missingFromPrd = REQUIRED_CATEGORIES.filter(c => !presentCategories.has(c));
85
85
 
86
86
  if (missingFromPrd.length > 0) {
@@ -90,6 +90,7 @@ if (missingFromPrd.length > 0) {
90
90
  let maxIdNum = Math.max(...prd.tasks.map(t => parseInt(t.id.replace(/[^0-9]/g, ''), 10) || 0));
91
91
  const prefix = prd.tasks[0]?.id?.replace(/[0-9]+$/, '') || 'GUARD-';
92
92
  const lastApiTask = prd.tasks.filter(t => t.category === 'api').pop()?.id;
93
+ const lastSeedDataTask = prd.tasks.filter(t => t.category === 'seedData').pop()?.id;
93
94
 
94
95
  for (const cat of missingFromPrd) {
95
96
  maxIdNum++;
@@ -98,7 +99,7 @@ if (missingFromPrd.length > 0) {
98
99
  id: taskId,
99
100
  description: `[GUARDRAIL] Generate missing ${cat} layer for ${prd.project?.module || 'module'}`,
100
101
  status: 'pending', category: cat,
101
- dependencies: lastApiTask ? [lastApiTask] : [],
102
+ dependencies: lastSeedDataTask ? [lastSeedDataTask] : (lastApiTask ? [lastApiTask] : []),
102
103
  acceptance_criteria: [
103
104
  cat === 'frontend' ? 'React pages + routes wired to App.tsx (standard + tenant blocks)' :
104
105
  cat === 'test' ? 'Unit + integration test projects with passing dotnet test' :
@@ -179,7 +180,7 @@ if (fileExists(queuePath)) {
179
180
  // MODULE COMPLETENESS CHECK (lightweight — heavy check already done in section 1.7)
180
181
  // Verify all expected layers have completed tasks
181
182
  const completedCats = new Set(prd.tasks.filter(t => t.status === 'completed').map(t => t.category));
182
- const expected = ['domain', 'infrastructure', 'application', 'api', 'frontend', 'test'];
183
+ const expected = ['domain', 'infrastructure', 'application', 'api', 'seedData', 'frontend', 'test'];
183
184
  const missing = expected.filter(c => !completedCats.has(c));
184
185
 
185
186
  if (missing.length > 0) {
@@ -70,7 +70,57 @@ if [ -d "$TEST_PROJECT" ]; then
70
70
  fi
71
71
  ```
72
72
 
73
- ### 1e. Final DB Validation (if infrastructure/migration tasks were executed)
73
+ ### 1e. Frontend Quality Metrics (if frontend tasks were executed)
74
+
75
+ ```bash
76
+ WEB_SRC=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
77
+ if [ -n "$WEB_SRC" ]; then
78
+ PAGE_DIR="$WEB_SRC/pages"
79
+ HOOK_DIR="$WEB_SRC/hooks"
80
+ COMP_DIR="$WEB_SRC/components"
81
+ I18N_DIR="$WEB_SRC/i18n/locales"
82
+
83
+ # Count metrics
84
+ PAGE_COUNT=$(find "$PAGE_DIR" -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null | wc -l)
85
+ TEST_COUNT=$(find "$PAGE_DIR" -name "*.test.tsx" 2>/dev/null | wc -l)
86
+ HOOK_COUNT=$(find "$HOOK_DIR" -name "*.ts" -o -name "*.tsx" 2>/dev/null | wc -l)
87
+
88
+ # Quality checks
89
+ RAW_TABLES=$(grep -rl '<table[\s>]' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | wc -l)
90
+ HARDCODED_COLORS=$(grep -rl '(?:bg|text|border)-(?:red|blue|green|gray|slate)-\d{2,3}' "$PAGE_DIR" "$COMP_DIR" --include="*.tsx" 2>/dev/null | wc -l)
91
+ CSS_VARS=$(grep -rl 'var(--' "$PAGE_DIR" "$COMP_DIR" --include="*.tsx" 2>/dev/null | wc -l)
92
+ WINDOW_CONFIRM=$(grep -rl 'window\.confirm' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | wc -l)
93
+ HARDCODED_TEXT=$(grep -rl '>\s*\(Create\|Edit\|Delete\|Save\|Cancel\)\s*<' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | wc -l)
94
+ USE_TRANSLATION=$(grep -rl 'useTranslation' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | wc -l)
95
+ SMART_TABLE=$(grep -rl 'SmartTable\|DataTable' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | wc -l)
96
+
97
+ # i18n coverage
98
+ I18N_LANGS=0
99
+ for LANG in fr en it de; do
100
+ [ -d "$I18N_DIR/$LANG" ] && I18N_LANGS=$((I18N_LANGS + 1))
101
+ done
102
+ fi
103
+ ```
104
+
105
+ Include frontend metrics in report section:
106
+ ```markdown
107
+ ## Frontend Quality
108
+ | Metric | Value | Target |
109
+ |--------|-------|--------|
110
+ | Pages | {PAGE_COUNT} | — |
111
+ | Tests | {TEST_COUNT} | ≥ {PAGE_COUNT/3} |
112
+ | Hooks | {HOOK_COUNT} | — |
113
+ | SmartTable/DataTable usage | {SMART_TABLE} files | All list pages |
114
+ | Raw HTML tables | {RAW_TABLES} files | 0 |
115
+ | CSS variable usage | {CSS_VARS} files | All TSX files |
116
+ | Hardcoded colors | {HARDCODED_COLORS} files | 0 |
117
+ | useTranslation() usage | {USE_TRANSLATION} files | All page files |
118
+ | Hardcoded English text | {HARDCODED_TEXT} files | 0 |
119
+ | window.confirm() usage | {WINDOW_CONFIRM} files | 0 |
120
+ | i18n languages | {I18N_LANGS}/4 | 4 |
121
+ ```
122
+
123
+ ### 1f. Final DB Validation (if infrastructure/migration tasks were executed)
74
124
 
75
125
  ```bash
76
126
  INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)