@atlashub/smartstack-cli 3.27.0 → 3.28.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/package.json +2 -3
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +1 -1
- package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +2 -2
- package/templates/skills/ralph-loop/references/category-rules.md +54 -3
- package/templates/skills/ralph-loop/references/compact-loop.md +108 -2
- package/templates/skills/ralph-loop/steps/step-02-execute.md +202 -14
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlashub/smartstack-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.28.0",
|
|
4
4
|
"description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SmartStack",
|
|
@@ -112,6 +112,5 @@
|
|
|
112
112
|
"typescript": "^5.3.3",
|
|
113
113
|
"vitest": "^2.1.0"
|
|
114
114
|
},
|
|
115
|
-
"optionalDependencies": {
|
|
116
|
-
}
|
|
115
|
+
"optionalDependencies": {}
|
|
117
116
|
}
|
|
@@ -491,7 +491,7 @@ const seedDataCore = {
|
|
|
491
491
|
),
|
|
492
492
|
|
|
493
493
|
navigationTranslations: master.modules.flatMap(m => {
|
|
494
|
-
const langs = ["fr", "en"]; //
|
|
494
|
+
const langs = ["fr", "en", "it", "de"]; // all supported languages
|
|
495
495
|
return langs.map(lang => ({
|
|
496
496
|
moduleCode: m.code,
|
|
497
497
|
language: lang,
|
|
@@ -450,7 +450,7 @@ Handoff Completeness:
|
|
|
450
450
|
For EACH module feature.json:
|
|
451
451
|
|
|
452
452
|
1. Extract all i18n keys from specification.i18nKeys[]
|
|
453
|
-
2. Verify each key has translations for: fr, en,
|
|
453
|
+
2. Verify each key has translations for: fr, en, it, de
|
|
454
454
|
3. Check for common missing patterns:
|
|
455
455
|
```
|
|
456
456
|
- Entity names (singular + plural)
|
|
@@ -467,7 +467,7 @@ For EACH module feature.json:
|
|
|
467
467
|
i18n Keys Validation:
|
|
468
468
|
Module: Projects
|
|
469
469
|
✓ 42 keys defined
|
|
470
|
-
✓ All keys have 4 languages (fr, en,
|
|
470
|
+
✓ All keys have 4 languages (fr, en, it, de)
|
|
471
471
|
✓ Entity keys complete
|
|
472
472
|
✓ Validation message keys complete
|
|
473
473
|
✓ Navigation keys complete
|
|
@@ -99,6 +99,18 @@ Execution sequence:
|
|
|
99
99
|
- Missing translations (must have all 4 languages)
|
|
100
100
|
- Seeding business entities WITHOUT `TenantId`
|
|
101
101
|
|
|
102
|
+
### POST-CHECK: Navigation translations diacritical marks
|
|
103
|
+
|
|
104
|
+
After generating `NavigationTranslationSeedEntry` strings, verify ALL non-English translations contain proper diacritical marks:
|
|
105
|
+
|
|
106
|
+
- **FR** must use: é, è, ê, ë, à, â, ç, ù, û, ô, î (e.g., "Employés" NOT "Employes", "Activités" NOT "Activites")
|
|
107
|
+
- **DE** must use: ä, ö, ü, ß (e.g., "Aktivitäten" NOT "Aktivitaten")
|
|
108
|
+
- **IT** must use: à, è, é, ì, ò, ù (e.g., "Attività" NOT "Attivita")
|
|
109
|
+
|
|
110
|
+
Cross-reference navigation seed data labels with the corresponding frontend `i18n/index.ts` translations for consistency.
|
|
111
|
+
|
|
112
|
+
**BLOCKING** if any `NavigationTranslationSeedEntry` for fr/de/it contains only ASCII characters where diacritical marks are expected in the target language.
|
|
113
|
+
|
|
102
114
|
---
|
|
103
115
|
|
|
104
116
|
## Infrastructure — SQL Objects
|
|
@@ -305,12 +317,13 @@ fi
|
|
|
305
317
|
**FORBIDDEN:** `src/pages/{Module}/` (flat structure without Context/App)
|
|
306
318
|
|
|
307
319
|
**Execution sequence (IN ORDER):**
|
|
320
|
+
0. **Read back feature.json** (via `prd.source.featurePath`) — The PRD only carries the file list (structural skeleton). The behavioral specs (columnDefs, componentMapping, layout, rowActions, emptyState, tab structure, i18n key-value pairs, dashboard KPIs, state machine transitions) are ONLY in the original feature.json. Without this step, generated code will be generic and miss entity-specific UI details.
|
|
308
321
|
1. `mcp__smartstack__scaffold_api_client` → API client + types + React Query hook
|
|
309
322
|
2. `mcp__smartstack__scaffold_routes` (with `outputFormat: "clientRoutes"`) → route registry + route fragments
|
|
310
323
|
3. **Wire routes to App.tsx (detect pattern: `contextRoutes` array OR JSX `<Route>`):**
|
|
311
324
|
- **Pattern A** (`contextRoutes: ContextRouteExtensions` in App.tsx): add to `contextRoutes.{context}[]` with RELATIVE paths → auto-injected into both standard + tenant trees
|
|
312
325
|
- **Pattern B** (JSX `<Route>` in App.tsx): insert `<Route>` entries inside correct Layout wrapper (BOTH standard AND tenant-prefixed `/t/:slug/...` blocks)
|
|
313
|
-
4. Create pages using **`/ui-components` skill**
|
|
326
|
+
4. Create pages using **`/ui-components` skill** — **LOAD the skill step files before creating any page.** This is MANDATORY for entity lists, grids, tables, dashboards, charts. The skill ensures CSS variables, EntityCard, SmartTable, loading/error/empty states, and responsive grid patterns. **Pages MUST use `t()` from `useTranslation()` for ALL visible text from the very first line of code.** Do NOT hardcode English strings with the intent of adding i18n later — the i18n keys are created in step 6 based on what keys the pages reference. Pattern: `t('{moduleLower}:actions.create', 'Create')`.
|
|
314
327
|
5. Create preferences hook: `use{Module}Preferences.ts`
|
|
315
328
|
6. Generate i18n (4 languages: fr, en, it, de) — see I18n section below for detailed rules
|
|
316
329
|
7. `npm run typecheck` MUST pass (BLOCKING)
|
|
@@ -323,6 +336,18 @@ fi
|
|
|
323
336
|
- FK fields: `EntityLookup` for searchable entity selection (NEVER plain text for Guid FK)
|
|
324
337
|
- Dashboard: `StatCard`, Recharts components
|
|
325
338
|
|
|
339
|
+
**Per-page /ui-components validation (MANDATORY before commit):**
|
|
340
|
+
- After creating EACH page, verify it follows /ui-components patterns:
|
|
341
|
+
1. List pages: uses `DataTable` or `SmartTable` (NOT raw `<table>`)
|
|
342
|
+
2. List pages: uses `EntityCard` for card/grid views (NOT custom `<div>` cards)
|
|
343
|
+
3. All pages: ALL colors use CSS variables (`bg-[var(--xxx)]`) — ZERO hardcoded Tailwind colors
|
|
344
|
+
4. All pages: loading skeleton, error state with retry, empty state are present
|
|
345
|
+
5. All pages: ALL visible text uses `t()` from `useTranslation()`
|
|
346
|
+
6. Delete actions: use `ConfirmDialog` component (NOT `window.confirm()`)
|
|
347
|
+
7. Status displays: use `StatusBadge` with CSS variable colors (NOT inline styled spans)
|
|
348
|
+
- **If ANY check fails: fix the page BEFORE moving to the next page**
|
|
349
|
+
- The POST-CHECKs in step-02 enforce these rules with BLOCKING severity
|
|
350
|
+
|
|
326
351
|
**Form pages (CRITICAL — ZERO modals/popups/drawers):**
|
|
327
352
|
- Create form: `EntityCreatePage.tsx` with route `/{module}/create`
|
|
328
353
|
- Edit form: `EntityEditPage.tsx` with route `/{module}/:id/edit`
|
|
@@ -340,6 +365,15 @@ fi
|
|
|
340
365
|
| `business.*` | `BusinessLayout` | `/business` |
|
|
341
366
|
| `personal.*` | `UserLayout` | `/personal/myspace` |
|
|
342
367
|
|
|
368
|
+
**Route naming convention (CRITICAL — must match backend):**
|
|
369
|
+
- Frontend route paths MUST use **kebab-case** matching the backend API route convention
|
|
370
|
+
- Convention: `HumanResources` (C# namespace) → `human-resources` (URL segment)
|
|
371
|
+
- ALL multi-word route segments MUST contain hyphens: `time-management`, `human-resources`, `leave-balance`
|
|
372
|
+
- FORBIDDEN: `humanresources`, `timemanagement`, `leavebalance` (concatenated without hyphens)
|
|
373
|
+
- Frontend path `/business/human-resources/clients` matches API `api/business/human-resources/clients`
|
|
374
|
+
- Navigation seed data Route values MUST also use kebab-case
|
|
375
|
+
- The POST-CHECK in step-02 will BLOCK if frontend routes don't match backend kebab-case convention
|
|
376
|
+
|
|
343
377
|
**CSS:** Variables ONLY → `bg-[var(--bg-card)]`, `text-[var(--text-primary)]`
|
|
344
378
|
|
|
345
379
|
**Form error handling (MANDATORY):**
|
|
@@ -348,6 +382,13 @@ fi
|
|
|
348
382
|
- API errors (4xx, 5xx) MUST show a user-friendly error notification (toast/banner)
|
|
349
383
|
- Forms MUST preserve user input on error (no data loss on failed submit)
|
|
350
384
|
|
|
385
|
+
**Hook error handling pattern (MANDATORY):**
|
|
386
|
+
- ALL hooks MUST preserve server validation errors from axios responses
|
|
387
|
+
- Pattern: `catch (err) { if (axios.isAxiosError(err)) { return err.response?.data; } throw err; }`
|
|
388
|
+
- NEVER discard `err.response.data` — it contains field-level validation errors from FluentValidation
|
|
389
|
+
- Error messages MUST use i18n: `setError(t('{mod}:errors.saveFailed'))` — NEVER hardcoded English strings
|
|
390
|
+
- Toast/notification messages MUST also use i18n
|
|
391
|
+
|
|
351
392
|
**Dependency verification (BLOCKING):**
|
|
352
393
|
- BEFORE writing any import, verify the package exists in `package.json`
|
|
353
394
|
- If package not present: run `npm install {package}` BEFORE writing the import
|
|
@@ -372,6 +413,13 @@ useXxx with raw axios inside hooks → hooks MUST use the shared apiCl
|
|
|
372
413
|
placeholder="Enter ID" / "Enter GUID" → EntityLookup provides search-based entity selection
|
|
373
414
|
<Modal>/<Dialog>/<Drawer>/<Popup> for forms → forms are FULL PAGES with own URL routes (/create, /:id/edit)
|
|
374
415
|
useState(showCreateModal/editDialog) → navigate to form page, NEVER toggle modal visibility
|
|
416
|
+
<table>/<thead>/<tbody> → MUST use SmartTable or DataTable component
|
|
417
|
+
bg-blue-600 / bg-red-500 / etc. → MUST use CSS variables: bg-[var(--color-accent-600)]
|
|
418
|
+
text-green-700 / text-gray-500 / etc. → MUST use CSS variables: text-[var(--success-text)]
|
|
419
|
+
window.confirm() / confirm() → MUST use ConfirmDialog component with i18n
|
|
420
|
+
Hardcoded English text in JSX (>Create<) → MUST use t('{mod}:actions.create', 'Create')
|
|
421
|
+
Hardcoded error strings in hooks → MUST use t('{mod}:errors.loadFailed') in all hooks
|
|
422
|
+
/business/humanresources/ → MUST use kebab-case: /business/human-resources/
|
|
375
423
|
```
|
|
376
424
|
|
|
377
425
|
---
|
|
@@ -399,11 +447,14 @@ useState(showCreateModal/editDialog) → navigate to form page, NEVER to
|
|
|
399
447
|
**Usage pattern in TSX:** `t('{moduleLower}:actions.create', 'Create')` — ALWAYS namespace prefix + fallback value.
|
|
400
448
|
|
|
401
449
|
**Rules:**
|
|
402
|
-
- 4 JSON files: fr, en, it, de — ALL mandatory (not just fr/en)
|
|
450
|
+
- 4 JSON files per module: fr, en, it, de — ALL mandatory (not just fr/en)
|
|
451
|
+
- File structure: `src/i18n/locales/{lang}/{moduleLower}.json` — NEVER inline translations in index.ts
|
|
403
452
|
- Identical key structures across all languages — same keys, translated values
|
|
404
|
-
- All UI labels, validation messages, button text, empty states
|
|
453
|
+
- All UI labels, validation messages, button text, empty states, error messages
|
|
405
454
|
- Depends on frontend page completion (pages define which keys are needed)
|
|
406
455
|
- Add entity-specific keys under `labels`, `columns`, `form` for each entity field
|
|
456
|
+
- **Hooks MUST also use i18n** — error messages like "Failed to load" MUST use `t('{mod}:errors.loadFailed')`
|
|
457
|
+
- **FORBIDDEN:** Inline translation objects in `i18n/index.ts` — use separate JSON files per language per module
|
|
407
458
|
- Reference `smartstack-frontend.md` for the complete template
|
|
408
459
|
|
|
409
460
|
---
|
|
@@ -69,6 +69,7 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
69
69
|
|
|
70
70
|
1. Mark `task.status = 'in_progress'`, `task.started_at = now`
|
|
71
71
|
2. **Read `references/category-rules.md`** for category-specific rules
|
|
72
|
+
2bis. **For frontend tasks:** Also read back the original **feature.json** (via `prd.source.featurePath`) to access wireframe `componentMapping`, `columnDefs`, `layout`, `emptyState`, `rowActions`, and `i18n` keys that the PRD does NOT carry forward. The PRD only has the file list — the behavioral spec is in feature.json.
|
|
72
73
|
3. Implement the task following SmartStack conventions
|
|
73
74
|
4. Track `files_created` and `files_modified`
|
|
74
75
|
|
|
@@ -110,7 +111,7 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
110
111
|
|----------|--------|
|
|
111
112
|
| `infrastructure` with `_migrationMeta` | Migration sequence: `suggest_migration` MCP → `dotnet ef migrations add` → cleanup corrupted artifacts (`for d in src/*/bin?Debug; do [ -d "$d" ] && rm -rf "$d"; done`) → `dotnet ef database update` → `dotnet build` |
|
|
112
113
|
| `infrastructure` with seed data keywords | **MANDATORY:** Read `references/core-seed-data.md` → implement templates → `dotnet build` |
|
|
113
|
-
| `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) → **wire to App.tsx
|
|
114
|
+
| `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) → **wire to App.tsx** → **LOAD `/ui-components` skill** before creating ANY page → create pages using skill patterns (SmartTable, EntityCard, CSS variables, StatusBadge) → ALL visible text via `t()` from first line → `npm run typecheck && npm run lint` → **run frontend POST-CHECKs (section 3bis below)** |
|
|
114
115
|
| `test` | **Create tests:** `scaffold_tests` MCP → **Run:** `dotnet test --verbosity normal` → **Fix loop:** if fail → fix SOURCE CODE → rebuild → retest → repeat until 100% pass |
|
|
115
116
|
| `validation` | `dotnet clean && dotnet restore && dotnet build` → `dotnet test` (full) → `validate_conventions` MCP |
|
|
116
117
|
|
|
@@ -161,7 +162,112 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
161
162
|
```
|
|
162
163
|
If FAIL → fix → re-check → loop until pass
|
|
163
164
|
|
|
164
|
-
3bis. **
|
|
165
|
+
3bis. **Frontend POST-CHECKs (BLOCKING — if frontend tasks in batch):**
|
|
166
|
+
|
|
167
|
+
> **CRITICAL:** These checks enforce /ui-components compliance, i18n, and route conventions.
|
|
168
|
+
> Without them, generated code silently regresses to raw HTML tables, hardcoded colors, and English-only text.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Resolve paths dynamically (handles web/ prefix)
|
|
172
|
+
PAGE_DIR=$(find . -path "*/src/pages" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
173
|
+
HOOK_DIR=$(find . -path "*/src/hooks" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
174
|
+
COMP_DIR=$(find . -path "*/src/components" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
175
|
+
WEB_SRC=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
|
|
176
|
+
|
|
177
|
+
if [ -n "$PAGE_DIR" ]; then
|
|
178
|
+
# BLOCKING: No raw HTML <table> — MUST use SmartTable/DataTable
|
|
179
|
+
RAW_TABLES=$(grep -rPn '<table[\s>]|<thead[\s>]|<tbody[\s>]' "$PAGE_DIR" --include="*.tsx" 2>/dev/null)
|
|
180
|
+
if [ -n "$RAW_TABLES" ]; then
|
|
181
|
+
echo "BLOCKING: Raw HTML <table> detected — MUST use SmartTable or DataTable"
|
|
182
|
+
echo "$RAW_TABLES" | head -5
|
|
183
|
+
# FIX REQUIRED before commit
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# BLOCKING: No hardcoded Tailwind colors — MUST use CSS variables
|
|
187
|
+
HARDCODED_COLORS=$(grep -rPn '(?:bg|text|border|ring)-(?: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}' "$PAGE_DIR" "$COMP_DIR" --include="*.tsx" 2>/dev/null | head -10)
|
|
188
|
+
if [ -n "$HARDCODED_COLORS" ]; then
|
|
189
|
+
echo "BLOCKING: Hardcoded Tailwind colors — MUST use CSS variables (bg-[var(--bg-secondary)])"
|
|
190
|
+
echo "$HARDCODED_COLORS" | head -5
|
|
191
|
+
# FIX REQUIRED before commit
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# BLOCKING: No window.confirm() — use ConfirmDialog component
|
|
195
|
+
BAD_CONFIRM=$(grep -rPn 'window\.confirm\s*\(' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | grep -v '//')
|
|
196
|
+
if [ -n "$BAD_CONFIRM" ]; then
|
|
197
|
+
echo "BLOCKING: window.confirm() detected — use ConfirmDialog component with i18n"
|
|
198
|
+
echo "$BAD_CONFIRM"
|
|
199
|
+
# FIX REQUIRED before commit
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# BLOCKING: No hardcoded English UI text in JSX
|
|
203
|
+
HARDCODED_TEXT=$(grep -rPn '>\s*(Create|Edit|Delete|Save|Cancel|Search|Loading|Error|No .* found|Are you sure|Submit|Back|Actions|Status)\s*<' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | grep -v test | grep -v '//' | head -10)
|
|
204
|
+
if [ -n "$HARDCODED_TEXT" ]; then
|
|
205
|
+
echo "BLOCKING: Hardcoded English text — ALL visible text MUST use t() from useTranslation()"
|
|
206
|
+
echo "$HARDCODED_TEXT" | head -5
|
|
207
|
+
# FIX REQUIRED before commit
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# BLOCKING: No modals/drawers for forms — forms are full pages
|
|
211
|
+
MODAL_IMPORTS=$(grep -rPn "import.*\b(Modal|Dialog|Drawer|Popup|Sheet)\b" "$PAGE_DIR" --include="*.tsx" 2>/dev/null)
|
|
212
|
+
if [ -n "$MODAL_IMPORTS" ]; then
|
|
213
|
+
echo "BLOCKING: Form modals/dialogs detected — forms MUST be full pages with own URL"
|
|
214
|
+
echo "$MODAL_IMPORTS"
|
|
215
|
+
# FIX REQUIRED before commit
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
if [ -n "$HOOK_DIR" ]; then
|
|
220
|
+
# BLOCKING: Hooks must use i18n for error messages
|
|
221
|
+
HARDCODED_ERRORS=$(grep -rPn "(setError|throw new Error)\(['\"][A-Z]" "$HOOK_DIR" --include="*.ts" --include="*.tsx" 2>/dev/null | grep -v "t(" | head -10)
|
|
222
|
+
if [ -n "$HARDCODED_ERRORS" ]; then
|
|
223
|
+
echo "BLOCKING: Hardcoded error messages in hooks — MUST use t('{mod}:errors.xxx')"
|
|
224
|
+
echo "$HARDCODED_ERRORS" | head -5
|
|
225
|
+
# FIX REQUIRED before commit
|
|
226
|
+
fi
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
# BLOCKING: i18n file structure — 4 languages with module-specific JSON files
|
|
230
|
+
if [ -n "$WEB_SRC" ]; then
|
|
231
|
+
I18N_DIR="$WEB_SRC/i18n/locales"
|
|
232
|
+
if [ -d "$I18N_DIR" ]; then
|
|
233
|
+
for LANG in fr en it de; do
|
|
234
|
+
if [ ! -d "$I18N_DIR/$LANG" ]; then
|
|
235
|
+
echo "BLOCKING: Missing i18n locale directory: $I18N_DIR/$LANG"
|
|
236
|
+
# FIX REQUIRED before commit
|
|
237
|
+
fi
|
|
238
|
+
MODULE_JSONS=$(find "$I18N_DIR/$LANG" -name "*.json" ! -name "common.json" ! -name "navigation.json" 2>/dev/null)
|
|
239
|
+
if [ -z "$MODULE_JSONS" ]; then
|
|
240
|
+
echo "BLOCKING: No module translation files in $I18N_DIR/$LANG/"
|
|
241
|
+
# FIX REQUIRED before commit
|
|
242
|
+
fi
|
|
243
|
+
done
|
|
244
|
+
else
|
|
245
|
+
echo "BLOCKING: Missing i18n/locales directory"
|
|
246
|
+
# FIX REQUIRED before commit
|
|
247
|
+
fi
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
# Cross-validate route conventions (frontend vs backend)
|
|
251
|
+
APP_TSX=$(find . -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
252
|
+
API_CONTROLLERS=$(find . -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
253
|
+
if [ -n "$APP_TSX" ] && [ -n "$API_CONTROLLERS" ]; then
|
|
254
|
+
API_SEGMENTS=$(grep -ohP '\[Route\("api/([^"]+)"\)\]' $API_CONTROLLERS | sed 's/\[Route("api\///;s/")\]//' | tr '/' '\n' | sort -u)
|
|
255
|
+
CLIENT_PATHS=$(grep -ohP "path:\s*['\"]([^'\"]+)['\"]" "$APP_TSX" | sed "s/path:\s*['\"]//;s/['\"]//" | sort -u)
|
|
256
|
+
for SEG in $API_SEGMENTS; do
|
|
257
|
+
if echo "$SEG" | grep -qP '[a-z]+-[a-z]+'; then
|
|
258
|
+
NO_HYPHEN=$(echo "$SEG" | tr -d '-')
|
|
259
|
+
if echo "$CLIENT_PATHS" | grep -q "$NO_HYPHEN"; then
|
|
260
|
+
echo "BLOCKING: Frontend uses '$NO_HYPHEN' but backend uses '$SEG' (kebab-case mismatch)"
|
|
261
|
+
# FIX REQUIRED before commit
|
|
262
|
+
fi
|
|
263
|
+
fi
|
|
264
|
+
done
|
|
265
|
+
fi
|
|
266
|
+
```
|
|
267
|
+
If ANY POST-CHECK fails → fix the code → re-run ALL checks → loop until ALL pass.
|
|
268
|
+
**NEVER commit with failing POST-CHECKs.**
|
|
269
|
+
|
|
270
|
+
3ter. **Dependency audit (if frontend tasks in batch):**
|
|
165
271
|
```bash
|
|
166
272
|
cd {frontend_dir}
|
|
167
273
|
npm ls --depth=0 2>&1 | grep "MISSING" && echo "FAIL: missing deps" && exit 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:**
|
|
@@ -244,9 +258,22 @@ npm run lint # ESLint — MUST exit 0
|
|
|
244
258
|
If FAIL → read errors → fix TSX/TS files → re-run → loop until pass.
|
|
245
259
|
|
|
246
260
|
**Frontend POST-CHECKs (BLOCKING for frontend category):**
|
|
261
|
+
|
|
262
|
+
> **Path resolution:** Web projects may live in subdirectories (e.g., `web/app-web/src/pages/`).
|
|
263
|
+
> All POST-CHECKs below use dynamic path discovery instead of hardcoded `src/pages/`.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# Resolve frontend directories dynamically (handles web/ prefix and varied project structures)
|
|
267
|
+
PAGE_DIR=$(find . -path "*/src/pages" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
268
|
+
HOOK_DIR=$(find . -path "*/src/hooks" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
269
|
+
COMP_DIR=$(find . -path "*/src/components" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
270
|
+
WEB_SRC=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
|
|
271
|
+
# All POST-CHECKs below use $PAGE_DIR, $HOOK_DIR, $COMP_DIR, $WEB_SRC instead of hardcoded paths
|
|
272
|
+
```
|
|
273
|
+
|
|
247
274
|
```bash
|
|
248
275
|
# POST-CHECK: Forms must be full pages — ZERO modals/popups/drawers
|
|
249
|
-
PAGE_FILES=$(find
|
|
276
|
+
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" 2>/dev/null)
|
|
250
277
|
if [ -n "$PAGE_FILES" ]; then
|
|
251
278
|
MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet)" $PAGE_FILES 2>/dev/null)
|
|
252
279
|
if [ -n "$MODAL_IMPORTS" ]; then
|
|
@@ -257,8 +284,92 @@ if [ -n "$PAGE_FILES" ]; then
|
|
|
257
284
|
fi
|
|
258
285
|
fi
|
|
259
286
|
|
|
287
|
+
# POST-CHECK: No raw HTML <table> in page files — MUST use SmartTable/DataTable (BLOCKING)
|
|
288
|
+
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" 2>/dev/null)
|
|
289
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
290
|
+
RAW_TABLES=$(grep -Pn '<table[\s>]|<thead[\s>]|<tbody[\s>]' $PAGE_FILES 2>/dev/null)
|
|
291
|
+
if [ -n "$RAW_TABLES" ]; then
|
|
292
|
+
echo "BLOCKING: Raw HTML <table> detected in page files — MUST use SmartTable or DataTable"
|
|
293
|
+
echo "SmartStack convention: Lists MUST use SmartTable + SmartFilter (see /ui-components skill)"
|
|
294
|
+
echo "Import: import { DataTable } from '@/components/ui/DataTable'"
|
|
295
|
+
echo ""
|
|
296
|
+
echo "Found:"
|
|
297
|
+
echo "$RAW_TABLES"
|
|
298
|
+
exit 1
|
|
299
|
+
fi
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
# POST-CHECK: No hardcoded Tailwind colors — MUST use CSS variables (BLOCKING)
|
|
303
|
+
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" -o -name "*.tsx" 2>/dev/null | sort -u)
|
|
304
|
+
COMPONENT_FILES=$(find "$COMP_DIR" -name "*.tsx" 2>/dev/null)
|
|
305
|
+
ALL_TSX="$PAGE_FILES $COMPONENT_FILES"
|
|
306
|
+
if [ -n "$ALL_TSX" ]; then
|
|
307
|
+
# Match bg-{color}-{shade} or text-{color}-{shade} patterns (excluding bg-white, bg-black, bg-transparent, bg-current)
|
|
308
|
+
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)
|
|
309
|
+
if [ -n "$HARDCODED_COLORS" ]; then
|
|
310
|
+
echo "BLOCKING: Hardcoded Tailwind colors detected — MUST use CSS variables"
|
|
311
|
+
echo "SmartStack convention: ALL colors via CSS variables for theming support"
|
|
312
|
+
echo " bg-blue-600 → bg-[var(--color-accent-600)]"
|
|
313
|
+
echo " text-red-500 → text-[var(--error-text)]"
|
|
314
|
+
echo " bg-green-100 → bg-[var(--success-bg)]"
|
|
315
|
+
echo " bg-gray-50 → bg-[var(--bg-secondary)]"
|
|
316
|
+
echo ""
|
|
317
|
+
echo "Found:"
|
|
318
|
+
echo "$HARDCODED_COLORS"
|
|
319
|
+
exit 1
|
|
320
|
+
fi
|
|
321
|
+
fi
|
|
322
|
+
|
|
323
|
+
# POST-CHECK: List pages must import SmartTable/DataTable OR EntityCard (WARNING)
|
|
324
|
+
LIST_PAGES=$(find "$PAGE_DIR" -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
|
|
325
|
+
if [ -n "$LIST_PAGES" ]; then
|
|
326
|
+
for LP in $LIST_PAGES; do
|
|
327
|
+
HAS_TABLE=$(grep -c 'SmartTable\|DataTable' "$LP" 2>/dev/null)
|
|
328
|
+
HAS_CARD=$(grep -c 'EntityCard' "$LP" 2>/dev/null)
|
|
329
|
+
if [ "$HAS_TABLE" -eq 0 ] && [ "$HAS_CARD" -eq 0 ]; then
|
|
330
|
+
echo "WARNING: List page missing SmartTable/DataTable or EntityCard: $LP"
|
|
331
|
+
echo "List pages MUST use SmartStack UI components from /ui-components skill"
|
|
332
|
+
echo " Tables: import { DataTable } from '@/components/ui/DataTable'"
|
|
333
|
+
echo " Cards: import { EntityCard } from '@/components/ui/EntityCard'"
|
|
334
|
+
fi
|
|
335
|
+
done
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
# POST-CHECK: No window.confirm() — MUST use confirmation component (BLOCKING)
|
|
339
|
+
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" 2>/dev/null)
|
|
340
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
341
|
+
BAD_CONFIRM=$(grep -Pn 'window\.confirm\s*\(|(?<!\w)confirm\s*\(' $PAGE_FILES 2>/dev/null | grep -v '//' | grep -v test)
|
|
342
|
+
if [ -n "$BAD_CONFIRM" ]; then
|
|
343
|
+
echo "BLOCKING: window.confirm() detected — MUST use a SmartStack confirmation component"
|
|
344
|
+
echo "Native browser dialogs break theming and i18n"
|
|
345
|
+
echo "Use a ConfirmDialog component with translated messages"
|
|
346
|
+
echo ""
|
|
347
|
+
echo "Found:"
|
|
348
|
+
echo "$BAD_CONFIRM"
|
|
349
|
+
exit 1
|
|
350
|
+
fi
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
# POST-CHECK: Pages must handle loading, error, and empty states (WARNING)
|
|
354
|
+
PAGE_FILES=$(find "$PAGE_DIR" -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
|
|
355
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
356
|
+
for LP in $PAGE_FILES; do
|
|
357
|
+
HAS_LOADING=$(grep -c 'loading\|isLoading\|skeleton\|Skeleton\|PageLoader\|spinner' "$LP" 2>/dev/null)
|
|
358
|
+
HAS_ERROR=$(grep -c 'error\|Error' "$LP" 2>/dev/null)
|
|
359
|
+
HAS_EMPTY=$(grep -c 'empty\|Empty\|no.*found\|length === 0' "$LP" 2>/dev/null)
|
|
360
|
+
MISSING=""
|
|
361
|
+
[ "$HAS_LOADING" -eq 0 ] && MISSING="${MISSING}loading "
|
|
362
|
+
[ "$HAS_ERROR" -eq 0 ] && MISSING="${MISSING}error "
|
|
363
|
+
[ "$HAS_EMPTY" -eq 0 ] && MISSING="${MISSING}empty "
|
|
364
|
+
if [ -n "$MISSING" ]; then
|
|
365
|
+
echo "WARNING: List page missing state handling ($MISSING): $LP"
|
|
366
|
+
echo "All list pages MUST handle: loading skeleton, error with retry, empty state"
|
|
367
|
+
fi
|
|
368
|
+
done
|
|
369
|
+
fi
|
|
370
|
+
|
|
260
371
|
# POST-CHECK: Create/Edit pages must exist for each module with a list page
|
|
261
|
-
LIST_PAGES=$(find
|
|
372
|
+
LIST_PAGES=$(find "$PAGE_DIR" -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test)
|
|
262
373
|
if [ -n "$LIST_PAGES" ]; then
|
|
263
374
|
for LP in $LIST_PAGES; do
|
|
264
375
|
DIR=$(dirname "$LP"); MOD=$(basename "$DIR")
|
|
@@ -272,9 +383,9 @@ if [ -n "$LIST_PAGES" ]; then
|
|
|
272
383
|
fi
|
|
273
384
|
|
|
274
385
|
# First check: at least one test file must exist in pages/
|
|
275
|
-
PAGE_FILES=$(find
|
|
386
|
+
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null)
|
|
276
387
|
if [ -n "$PAGE_FILES" ]; then
|
|
277
|
-
TEST_FILES=$(find
|
|
388
|
+
TEST_FILES=$(find "$PAGE_DIR" -name "*.test.tsx" 2>/dev/null)
|
|
278
389
|
if [ -z "$TEST_FILES" ]; then
|
|
279
390
|
echo "BLOCKING: No frontend test files found in src/pages/"
|
|
280
391
|
echo "Every module with pages MUST have at least one .test.tsx file"
|
|
@@ -283,7 +394,7 @@ if [ -n "$PAGE_FILES" ]; then
|
|
|
283
394
|
fi
|
|
284
395
|
|
|
285
396
|
# POST-CHECK: Form pages must have companion test files
|
|
286
|
-
FORM_PAGES=$(find
|
|
397
|
+
FORM_PAGES=$(find "$PAGE_DIR" -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test)
|
|
287
398
|
if [ -n "$FORM_PAGES" ]; then
|
|
288
399
|
for FP in $FORM_PAGES; do
|
|
289
400
|
TEST="${FP%.tsx}.test.tsx"
|
|
@@ -295,7 +406,7 @@ if [ -n "$FORM_PAGES" ]; then
|
|
|
295
406
|
fi
|
|
296
407
|
|
|
297
408
|
# POST-CHECK: FK fields must NOT be plain text inputs — use EntityLookup
|
|
298
|
-
FORM_PAGES=$(find
|
|
409
|
+
FORM_PAGES=$(find "$PAGE_DIR" -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test | grep -v node_modules)
|
|
299
410
|
if [ -n "$FORM_PAGES" ]; then
|
|
300
411
|
# Match any input with name ending in "Id" (except hidden inputs)
|
|
301
412
|
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 +437,34 @@ if [ -n "$CTRL_FILES" ]; then
|
|
|
326
437
|
done
|
|
327
438
|
fi
|
|
328
439
|
|
|
329
|
-
# POST-CHECK: Route seed data vs frontend cross-validation
|
|
440
|
+
# POST-CHECK: Route seed data vs frontend cross-validation (CONVENTION + EXISTENCE)
|
|
330
441
|
SEED_NAV_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" 2>/dev/null)
|
|
331
|
-
APP_TSX
|
|
442
|
+
APP_TSX="$WEB_SRC/App.tsx"
|
|
332
443
|
if [ -n "$SEED_NAV_FILES" ] && [ -n "$APP_TSX" ]; then
|
|
333
444
|
SEED_ROUTES=$(grep -ohP 'Route\s*=\s*"([^"]+)"' $SEED_NAV_FILES | sed 's/Route\s*=\s*"//' | sed 's/"//' | sort -u)
|
|
334
445
|
CLIENT_PATHS=$(grep -ohP "path:\s*['\"]([^'\"]+)['\"]" "$APP_TSX" | sed "s/path:\s*['\"]//;s/['\"]//" | sort -u)
|
|
335
446
|
if [ -n "$SEED_ROUTES" ] && [ -z "$CLIENT_PATHS" ]; then
|
|
336
447
|
echo "WARNING: Seed data has navigation routes but App.tsx has no client routes defined"
|
|
337
448
|
fi
|
|
449
|
+
# Cross-check: frontend paths must use same kebab-case segments as backend API routes
|
|
450
|
+
API_CONTROLLERS=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
451
|
+
if [ -n "$API_CONTROLLERS" ] && [ -n "$CLIENT_PATHS" ]; then
|
|
452
|
+
API_SEGMENTS=$(grep -ohP '\[Route\("api/([^"]+)"\)\]' $API_CONTROLLERS | sed 's/\[Route("api\///;s/")\]//' | tr '/' '\n' | sort -u)
|
|
453
|
+
for SEG in $API_SEGMENTS; do
|
|
454
|
+
# Check if frontend path contains same segment (kebab-case match)
|
|
455
|
+
if echo "$SEG" | grep -qP '[a-z]+-[a-z]+'; then
|
|
456
|
+
# Multi-word segment like "human-resources" — verify frontend uses same convention
|
|
457
|
+
NO_HYPHEN=$(echo "$SEG" | tr -d '-')
|
|
458
|
+
if echo "$CLIENT_PATHS" | grep -q "$NO_HYPHEN"; then
|
|
459
|
+
echo "BLOCKING: Frontend route uses '$NO_HYPHEN' but backend API uses '$SEG' (kebab-case)"
|
|
460
|
+
echo "Fix: Frontend paths MUST match backend route convention"
|
|
461
|
+
echo " Backend: api/business/$SEG/..."
|
|
462
|
+
echo " Frontend: /business/$SEG/... (NOT /business/$NO_HYPHEN/...)"
|
|
463
|
+
exit 1
|
|
464
|
+
fi
|
|
465
|
+
fi
|
|
466
|
+
done
|
|
467
|
+
fi
|
|
338
468
|
fi
|
|
339
469
|
|
|
340
470
|
# POST-CHECK: HasQueryFilter anti-pattern
|
|
@@ -359,20 +489,78 @@ if [ -n "$SERVICE_FILES" ]; then
|
|
|
359
489
|
fi
|
|
360
490
|
fi
|
|
361
491
|
|
|
362
|
-
# POST-CHECK: i18n
|
|
363
|
-
|
|
364
|
-
if [ -n "$
|
|
365
|
-
|
|
492
|
+
# POST-CHECK: i18n file structure — per-module JSON files MUST exist (BLOCKING)
|
|
493
|
+
WEB_DIR=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
|
|
494
|
+
if [ -n "$WEB_DIR" ]; then
|
|
495
|
+
I18N_DIR="$WEB_DIR/i18n/locales"
|
|
496
|
+
if [ -d "$I18N_DIR" ]; then
|
|
497
|
+
for LANG in fr en it de; do
|
|
498
|
+
LANG_DIR="$I18N_DIR/$LANG"
|
|
499
|
+
if [ ! -d "$LANG_DIR" ]; then
|
|
500
|
+
echo "BLOCKING: Missing i18n locale directory: $LANG_DIR"
|
|
501
|
+
echo "All 4 languages (fr, en, it, de) MUST have a dedicated directory"
|
|
502
|
+
exit 1
|
|
503
|
+
fi
|
|
504
|
+
# Check for at least one module-specific JSON (not just common.json)
|
|
505
|
+
MODULE_JSONS=$(find "$LANG_DIR" -name "*.json" ! -name "common.json" ! -name "navigation.json" 2>/dev/null)
|
|
506
|
+
if [ -z "$MODULE_JSONS" ]; then
|
|
507
|
+
echo "BLOCKING: No module translation files in $LANG_DIR/"
|
|
508
|
+
echo "Each module MUST have src/i18n/locales/$LANG/{moduleLower}.json"
|
|
509
|
+
exit 1
|
|
510
|
+
fi
|
|
511
|
+
done
|
|
512
|
+
else
|
|
513
|
+
echo "BLOCKING: Missing i18n/locales directory — i18n file structure not created"
|
|
514
|
+
echo "Expected: src/i18n/locales/{fr,en,it,de}/{module}.json"
|
|
515
|
+
exit 1
|
|
516
|
+
fi
|
|
517
|
+
fi
|
|
518
|
+
|
|
519
|
+
# POST-CHECK: i18n required key structure (BLOCKING — upgraded from WARNING)
|
|
520
|
+
if [ -d "$I18N_DIR/fr" ]; then
|
|
521
|
+
for f in $(find "$I18N_DIR/fr/" -name "*.json" 2>/dev/null); do
|
|
366
522
|
BASENAME=$(basename "$f")
|
|
367
523
|
case "$BASENAME" in common.json|navigation.json) continue;; esac
|
|
368
|
-
for KEY in "actions" "labels" "errors"; do
|
|
524
|
+
for KEY in "actions" "labels" "errors" "messages" "empty"; do
|
|
369
525
|
if ! grep -q "\"$KEY\"" "$f"; then
|
|
370
|
-
echo "
|
|
526
|
+
echo "BLOCKING: i18n file missing required key group '$KEY': $f"
|
|
527
|
+
echo "All module i18n files MUST contain: actions, labels, errors, messages, empty"
|
|
528
|
+
exit 1
|
|
371
529
|
fi
|
|
372
530
|
done
|
|
373
531
|
done
|
|
374
532
|
fi
|
|
375
533
|
|
|
534
|
+
# POST-CHECK: Pages must NOT contain hardcoded English UI text (BLOCKING)
|
|
535
|
+
PAGE_FILES=$(find "$PAGE_DIR" -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null)
|
|
536
|
+
if [ -n "$PAGE_FILES" ]; then
|
|
537
|
+
# Detect common hardcoded English patterns in JSX (excluding imports/comments/code)
|
|
538
|
+
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)
|
|
539
|
+
if [ -n "$HARDCODED_TEXT" ]; then
|
|
540
|
+
echo "BLOCKING: Hardcoded English UI text found in page files"
|
|
541
|
+
echo "ALL visible text MUST use t() from useTranslation() — NEVER hardcode strings"
|
|
542
|
+
echo "Pattern: t('{module}:actions.create', 'Create') — namespace + fallback"
|
|
543
|
+
echo ""
|
|
544
|
+
echo "Found:"
|
|
545
|
+
echo "$HARDCODED_TEXT"
|
|
546
|
+
exit 1
|
|
547
|
+
fi
|
|
548
|
+
fi
|
|
549
|
+
|
|
550
|
+
# POST-CHECK: Hooks must NOT contain hardcoded error messages (BLOCKING)
|
|
551
|
+
HOOK_FILES=$(find "$HOOK_DIR" -name "*.ts" -o -name "*.tsx" 2>/dev/null)
|
|
552
|
+
if [ -n "$HOOK_FILES" ]; then
|
|
553
|
+
HARDCODED_ERRORS=$(grep -Pn "(setError|throw new Error)\(['\"](?!t\()[A-Z][a-z]+" $HOOK_FILES 2>/dev/null | head -15)
|
|
554
|
+
if [ -n "$HARDCODED_ERRORS" ]; then
|
|
555
|
+
echo "BLOCKING: Hardcoded error messages in hooks — MUST use i18n t() function"
|
|
556
|
+
echo "Hooks must import useTranslation and use t('{module}:errors.loadFailed') for all messages"
|
|
557
|
+
echo ""
|
|
558
|
+
echo "Found:"
|
|
559
|
+
echo "$HARDCODED_ERRORS"
|
|
560
|
+
exit 1
|
|
561
|
+
fi
|
|
562
|
+
fi
|
|
563
|
+
|
|
376
564
|
# POST-CHECK: SeedConstants must NOT contain ContextId (pre-seeded by SmartStack core)
|
|
377
565
|
SEED_CONST_FILES=$(find src/ -path "*/Seeding/*" -name "SeedConstants.cs" 2>/dev/null)
|
|
378
566
|
if [ -n "$SEED_CONST_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)
|