@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.
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -3
- package/templates/project/api.ts.template +4 -2
- package/templates/project/appsettings.json.template +1 -1
- package/templates/skills/apex/_shared.md +13 -0
- package/templates/skills/apex/references/post-checks.md +228 -6
- package/templates/skills/apex/references/smartstack-api.md +67 -17
- package/templates/skills/apex/references/smartstack-frontend.md +41 -1
- package/templates/skills/apex/references/smartstack-layers.md +40 -10
- package/templates/skills/apex/steps/step-02-plan.md +16 -11
- package/templates/skills/apex/steps/step-03-execute.md +6 -0
- package/templates/skills/apex/steps/step-04-examine.md +4 -2
- package/templates/skills/application/references/frontend-verification.md +26 -1
- package/templates/skills/application/steps/step-03-roles.md +1 -1
- package/templates/skills/application/steps/step-05-frontend.md +24 -8
- package/templates/skills/application/templates-frontend.md +41 -22
- package/templates/skills/application/templates-seed.md +53 -16
- package/templates/skills/business-analyse/SKILL.md +4 -2
- package/templates/skills/business-analyse/_shared.md +17 -4
- package/templates/skills/business-analyse/react/schema.md +1 -1
- package/templates/skills/business-analyse/references/agent-module-prompt.md +11 -9
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +4 -3
- package/templates/skills/business-analyse/references/deploy-modes.md +1 -1
- package/templates/skills/business-analyse/references/handoff-file-templates.md +4 -4
- package/templates/skills/business-analyse/references/robustness-checks.md +12 -9
- package/templates/skills/business-analyse/references/spec-auto-inference.md +3 -3
- package/templates/skills/business-analyse/references/ui-resource-cards.md +3 -3
- package/templates/skills/business-analyse/references/validation-checklist.md +21 -3
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +33 -5
- package/templates/skills/business-analyse/steps/step-03b-ui.md +2 -2
- package/templates/skills/business-analyse/steps/step-03c-compile.md +17 -9
- package/templates/skills/business-analyse/steps/step-03d-validate.md +1 -1
- package/templates/skills/business-analyse/steps/step-04b-analyze.md +5 -3
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +24 -16
- package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +2 -2
- package/templates/skills/business-analyse/templates/tpl-handoff.md +10 -8
- package/templates/skills/business-analyse/templates/tpl-progress.md +7 -6
- package/templates/skills/ralph-loop/references/category-rules.md +104 -9
- package/templates/skills/ralph-loop/references/compact-loop.md +124 -3
- package/templates/skills/ralph-loop/references/core-seed-data.md +158 -38
- package/templates/skills/ralph-loop/references/task-transform-legacy.md +3 -3
- package/templates/skills/ralph-loop/steps/step-02-execute.md +311 -15
- package/templates/skills/ralph-loop/steps/step-03-commit.md +2 -2
- package/templates/skills/ralph-loop/steps/step-04-check.md +4 -3
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
363
|
-
|
|
364
|
-
if [ -n "$
|
|
365
|
-
|
|
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 "
|
|
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
|
|
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.
|
|
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)
|