@atlashub/smartstack-cli 4.32.0 → 4.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.documentation/index.html +2 -2
  2. package/.documentation/init.html +358 -174
  3. package/dist/index.js +45 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/mcp-entry.mjs +271 -44
  6. package/dist/mcp-entry.mjs.map +1 -1
  7. package/package.json +1 -1
  8. package/templates/mcp-scaffolding/controller.cs.hbs +54 -128
  9. package/templates/project/README.md +19 -0
  10. package/templates/project/claude-md/api.CLAUDE.md.template +315 -0
  11. package/templates/project/claude-md/application.CLAUDE.md.template +181 -0
  12. package/templates/project/claude-md/domain.CLAUDE.md.template +125 -0
  13. package/templates/project/claude-md/infrastructure.CLAUDE.md.template +168 -0
  14. package/templates/project/claude-md/root.CLAUDE.md.template +339 -0
  15. package/templates/project/claude-md/web.CLAUDE.md.template +339 -0
  16. package/templates/skills/apex/SKILL.md +16 -10
  17. package/templates/skills/apex/_shared.md +1 -1
  18. package/templates/skills/apex/references/checks/architecture-checks.sh +154 -0
  19. package/templates/skills/apex/references/checks/backend-checks.sh +194 -0
  20. package/templates/skills/apex/references/checks/frontend-checks.sh +448 -0
  21. package/templates/skills/apex/references/checks/infrastructure-checks.sh +255 -0
  22. package/templates/skills/apex/references/checks/security-checks.sh +153 -0
  23. package/templates/skills/apex/references/checks/seed-checks.sh +536 -0
  24. package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +49 -192
  25. package/templates/skills/apex/references/post-checks.md +124 -2156
  26. package/templates/skills/apex/references/smartstack-api.md +160 -957
  27. package/templates/skills/apex/references/smartstack-frontend.md +134 -1022
  28. package/templates/skills/apex/references/smartstack-layers.md +12 -6
  29. package/templates/skills/apex/steps/step-00-init.md +81 -238
  30. package/templates/skills/apex/steps/step-03-execute.md +25 -752
  31. package/templates/skills/apex/steps/step-03a-layer0-domain.md +118 -0
  32. package/templates/skills/apex/steps/step-03b-layer1-seed.md +91 -0
  33. package/templates/skills/apex/steps/step-03c-layer2-backend.md +240 -0
  34. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +300 -0
  35. package/templates/skills/apex/steps/step-03e-layer4-devdata.md +44 -0
  36. package/templates/skills/apex/steps/step-04-examine.md +70 -150
  37. package/templates/skills/application/references/frontend-i18n-and-output.md +2 -2
  38. package/templates/skills/application/references/frontend-route-naming.md +5 -1
  39. package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +49 -198
  40. package/templates/skills/application/references/frontend-verification.md +11 -11
  41. package/templates/skills/application/steps/step-05-frontend.md +26 -15
  42. package/templates/skills/application/templates-frontend.md +4 -0
  43. package/templates/skills/cli-app-sync/SKILL.md +2 -2
  44. package/templates/skills/cli-app-sync/references/comparison-map.md +1 -1
  45. package/templates/skills/controller/references/controller-code-templates.md +70 -67
  46. package/templates/skills/controller/references/mcp-scaffold-workflow.md +5 -1
@@ -0,0 +1,448 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # POST-CHECK: Frontend — CSS, Forms, Components, I18n
5
+ # C3a, C3-C7, C9, C11, C24-C27, C36-C37, C49, C52: Page structure, forms, i18n, routing
6
+
7
+ FAIL=false
8
+
9
+ # POST-CHECK C3a: Frontend must not be empty if Layer 3 was planned (BLOCKING)
10
+ APP_TSX=$(find web/ src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
11
+ if [ -n "$APP_TSX" ]; then
12
+ # Check for DynamicRouter pattern (v3.7+)
13
+ COMPONENT_REGISTRY=$(find web/ src/ -name "componentRegistry.generated.ts" -not -path "*/node_modules/*" 2>/dev/null | head -1)
14
+ if [ -n "$COMPONENT_REGISTRY" ]; then
15
+ echo "OK: componentRegistry.generated.ts found (DynamicRouter pattern)"
16
+ else
17
+ EMPTY_ROUTES=$(grep -P "applicationRoutes.*=\s*\{[\s/]*\}" "$APP_TSX" 2>/dev/null || true)
18
+ if [ -n "$EMPTY_ROUTES" ]; then
19
+ echo "BLOCKING: applicationRoutes in App.tsx is empty — Layer 3 frontend was NOT executed"
20
+ echo "Expected: at least one application key with route definitions"
21
+ echo "Fix: Run Layer 3 (scaffold_routes + scaffold_extension)"
22
+ FAIL=true
23
+ fi
24
+ fi
25
+
26
+ PAGE_COUNT=$(find web/ src/ -path "*/pages/*" -name "*.tsx" -not -path "*/node_modules/*" 2>/dev/null | wc -l)
27
+ if [ "$PAGE_COUNT" -eq 0 ]; then
28
+ echo "BLOCKING: No page components found in pages/ directory"
29
+ echo "Fix: Generate pages via scaffold_extension or /ui-components"
30
+ FAIL=true
31
+ fi
32
+
33
+ NAV_ROUTES=$(find web/ src/ -name "navRoutes.generated.ts" -not -path "*/node_modules/*" 2>/dev/null | head -1)
34
+ if [ -z "$NAV_ROUTES" ]; then
35
+ echo "BLOCKING: navRoutes.generated.ts not found — scaffold_routes was never called"
36
+ echo "Fix: Run scaffold_routes(source: 'controllers', outputFormat: 'componentRegistry')"
37
+ FAIL=true
38
+ fi
39
+ fi
40
+
41
+ # POST-CHECK C3: Translation files must exist for all 4 languages (if frontend)
42
+ TSX_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null || true)
43
+ if [ -n "$TSX_FILES" ]; then
44
+ NAMESPACES=$(grep -ohP "useTranslation\(\[?'([^']+)" $TSX_FILES 2>/dev/null | sed "s/.*'//" | sort -u || true)
45
+ for NS in $NAMESPACES; do
46
+ for LANG in fr en it de; do
47
+ if [ ! -f "src/i18n/locales/$LANG/$NS.json" ]; then
48
+ echo "WARNING: Missing translation file: src/i18n/locales/$LANG/$NS.json"
49
+ fi
50
+ done
51
+ done
52
+ fi
53
+
54
+ # POST-CHECK C4: Forms must be full pages with routes — ZERO modals/popups/drawers/slide-overs
55
+ PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null || true)
56
+ if [ -n "$PAGE_FILES" ]; then
57
+ MODAL_IMPORTS=$(grep -Pn "import.*(?:Modal|Dialog|Drawer|Popup|Sheet|SlideOver|Overlay)" $PAGE_FILES 2>/dev/null || true)
58
+ if [ -n "$MODAL_IMPORTS" ]; then
59
+ echo "WARNING: Form pages must NOT use Modal/Dialog/Drawer/Popup/SlideOver components"
60
+ echo "$MODAL_IMPORTS"
61
+ FAIL=true
62
+ fi
63
+
64
+ FORM_STATE=$(grep -Pn "useState.*(?:isOpen|showModal|showDialog|showCreate|showEdit|showForm|isCreating|isEditing|showDrawer|showPanel|showSlideOver|selectedEntity|editingEntity)" $PAGE_FILES 2>/dev/null || true)
65
+ if [ -n "$FORM_STATE" ]; then
66
+ echo "WARNING: Inline form state detected — forms embedded in ListPage as drawers/panels"
67
+ echo "Create/Edit forms MUST be separate page components with their own URL routes"
68
+ echo "$FORM_STATE"
69
+ FAIL=true
70
+ fi
71
+
72
+ if [ "$FAIL" = true ]; then
73
+ echo ""
74
+ echo "Fix: Create EntityCreatePage.tsx with route /create and EntityEditPage.tsx with route /:id/edit"
75
+ echo "NEVER embed create/edit forms as inline drawers, panels, or slide-overs in ListPage"
76
+ fi
77
+ fi
78
+
79
+ # POST-CHECK C5: Create/Edit pages must exist as separate route pages (CRITICAL)
80
+ LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v test || true)
81
+ if [ -n "$LIST_PAGES" ]; then
82
+ FAIL_C5=false
83
+ for LIST_PAGE in $LIST_PAGES; do
84
+ PAGE_DIR=$(dirname "$LIST_PAGE")
85
+ MODULE_NAME=$(basename "$PAGE_DIR")
86
+
87
+ HAS_CREATE_NAV=$(grep -P "navigate\(.*['/]create" "$LIST_PAGE" 2>/dev/null || true)
88
+ HAS_EDIT_NAV=$(grep -P "navigate\(.*['/]edit|navigate\(.*/:id/edit" "$LIST_PAGE" 2>/dev/null || true)
89
+
90
+ CREATE_PAGE=$(find "$PAGE_DIR" -name "*CreatePage.tsx" 2>/dev/null || true)
91
+ if [ -z "$CREATE_PAGE" ]; then
92
+ if [ -n "$HAS_CREATE_NAV" ]; then
93
+ echo "CRITICAL: Module $MODULE_NAME ListPage navigates to /create but CreatePage does NOT exist"
94
+ echo " Dead link: $HAS_CREATE_NAV"
95
+ echo " Fix: Create ${MODULE_NAME}CreatePage.tsx in $PAGE_DIR"
96
+ FAIL_C5=true
97
+ fi
98
+ fi
99
+
100
+ EDIT_PAGE=$(find "$PAGE_DIR" -name "*EditPage.tsx" 2>/dev/null || true)
101
+ if [ -z "$EDIT_PAGE" ]; then
102
+ if [ -n "$HAS_EDIT_NAV" ]; then
103
+ echo "CRITICAL: Module $MODULE_NAME ListPage navigates to /:id/edit but EditPage does NOT exist"
104
+ echo " Dead link: $HAS_EDIT_NAV"
105
+ echo " Fix: Create ${MODULE_NAME}EditPage.tsx in $PAGE_DIR"
106
+ FAIL_C5=true
107
+ fi
108
+ fi
109
+ done
110
+
111
+ if [ "$FAIL_C5" = true ]; then
112
+ echo ""
113
+ echo "CRITICAL: Create/Edit pages are MISSING but ListPage buttons link to them."
114
+ echo "Users will see white screen / 404 when clicking Create or Edit buttons."
115
+ echo "Fix: Generate form pages using /ui-components skill patterns (smartstack-frontend.md section 3b)"
116
+ FAIL=true
117
+ fi
118
+ fi
119
+
120
+ # POST-CHECK C6: Form pages must have companion test files
121
+ PAGE_FILES=$(find src/pages/ -name "*.tsx" ! -name "*.test.tsx" 2>/dev/null || true)
122
+ if [ -n "$PAGE_FILES" ]; then
123
+ ALL_TESTS=$(find src/pages/ -name "*.test.tsx" 2>/dev/null || true)
124
+ if [ -z "$ALL_TESTS" ]; then
125
+ echo "WARNING: No frontend test files found in src/pages/"
126
+ echo "Every form page MUST have a companion .test.tsx file"
127
+ fi
128
+ fi
129
+
130
+ FORM_PAGES=$(find src/pages/ -name "*CreatePage.tsx" -o -name "*EditPage.tsx" 2>/dev/null | grep -v test || true)
131
+ if [ -n "$FORM_PAGES" ]; then
132
+ for FORM_PAGE in $FORM_PAGES; do
133
+ TEST_FILE="${FORM_PAGE%.tsx}.test.tsx"
134
+ if [ ! -f "$TEST_FILE" ]; then
135
+ echo "WARNING: Form page missing test file: $FORM_PAGE"
136
+ echo "Expected: $TEST_FILE"
137
+ echo "All form pages MUST have companion test files (rendering, validation, submit, navigation)"
138
+ fi
139
+ done
140
+ fi
141
+
142
+ # POST-CHECK C7: FK fields must use EntityLookup — NO <input>, NO <select> (WARNING)
143
+ ALL_PAGES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
144
+ if [ -n "$ALL_PAGES" ]; then
145
+ FAIL_C7=false
146
+
147
+ FK_INPUTS=$(grep -Pn '<input[^>]*(?:name|value)=["\x27{][^>]*[a-zA-Z]Id["\x27}]' $ALL_PAGES 2>/dev/null | grep -Pv 'type=["\x27]hidden["\x27]' || true)
148
+ if [ -n "$FK_INPUTS" ]; then
149
+ echo "WARNING: FK fields rendered as <input> — MUST use EntityLookup component"
150
+ echo "$FK_INPUTS"
151
+ FAIL_C7=true
152
+ fi
153
+
154
+ FK_SELECTS=$(grep -Pn '<select[^>]*value=\{[^}]*[a-zA-Z]Id\b' $ALL_PAGES 2>/dev/null || true)
155
+ if [ -n "$FK_SELECTS" ]; then
156
+ echo "WARNING: FK fields rendered as <select> dropdown — MUST use EntityLookup component"
157
+ echo "A <select> loaded from API state is NOT a valid substitute for EntityLookup."
158
+ echo "EntityLookup provides: debounced search, paginated results, display name resolution."
159
+ echo "$FK_SELECTS"
160
+ FAIL_C7=true
161
+ fi
162
+
163
+ FK_SELECT_ONCHANGE=$(grep -Pn 'onChange=.*[a-zA-Z]Id[^a-zA-Z].*e\.target\.value' $ALL_PAGES 2>/dev/null || true)
164
+ if [ -n "$FK_SELECT_ONCHANGE" ]; then
165
+ echo "WARNING: FK field set via e.target.value (select/input pattern) — use EntityLookup onChange(id)"
166
+ echo "$FK_SELECT_ONCHANGE"
167
+ FAIL_C7=true
168
+ fi
169
+
170
+ FK_PLACEHOLDER=$(grep -Pn 'placeholder=["\x27].*(?:[Ee]nter.*[Ii][Dd]|[Gg][Uu][Ii][Dd]|[Ss]elect.*[Ee]mployee|[Ss]elect.*[Dd]epartment|[Ss]elect.*[Pp]osition|[Ss]elect.*[Pp]roject|[Ss]elect.*[Cc]ategory)' $ALL_PAGES 2>/dev/null || true)
171
+ if [ -n "$FK_PLACEHOLDER" ]; then
172
+ echo "WARNING: Form has placeholder for FK field selection — use EntityLookup search instead"
173
+ echo "$FK_PLACEHOLDER"
174
+ FAIL_C7=true
175
+ fi
176
+
177
+ FK_OPTIONS=$(grep -Pn '<option[^>]*value=\{[^}]*\.id\}' $ALL_PAGES 2>/dev/null || true)
178
+ if [ -n "$FK_OPTIONS" ]; then
179
+ echo "WARNING: <option> with entity .id value detected — this is a FK <select> anti-pattern"
180
+ echo "Replace the entire <select>/<option> block with <EntityLookup />"
181
+ echo "$FK_OPTIONS"
182
+ FAIL_C7=true
183
+ fi
184
+
185
+ if [ "$FAIL_C7" = true ]; then
186
+ echo ""
187
+ echo "Fix: Replace ALL FK fields with <EntityLookup /> from @/components/ui/EntityLookup"
188
+ echo "See smartstack-frontend.md section 6 for the EntityLookup pattern"
189
+ echo "FORBIDDEN for FK Guid fields: <input>, <select>, <option>, e.target.value"
190
+ echo "REQUIRED: <EntityLookup apiEndpoint={...} mapOption={...} value={...} onChange={...} />"
191
+ fi
192
+ fi
193
+
194
+ # POST-CHECK C9: No hardcoded Tailwind colors in generated pages (WARNING)
195
+ ALL_PAGES=$(find src/pages/ src/components/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
196
+ if [ -n "$ALL_PAGES" ]; then
197
+ HARDCODED=$(grep -Pn '(bg|text|border)-(?!\[)(red|blue|green|gray|white|black|slate|zinc|neutral|stone)-' $ALL_PAGES 2>/dev/null || true)
198
+ if [ -n "$HARDCODED" ]; then
199
+ echo "WARNING: Pages MUST use CSS variables instead of hardcoded Tailwind colors"
200
+ echo "SmartStack uses a theme system — hardcoded colors break dark mode and custom themes"
201
+ echo ""
202
+ echo "Fix mapping:"
203
+ echo " bg-white → bg-[var(--bg-card)]"
204
+ echo " bg-gray-50 → bg-[var(--bg-primary)]"
205
+ echo " text-gray-900 → text-[var(--text-primary)]"
206
+ echo " text-gray-500 → text-[var(--text-secondary)]"
207
+ echo " border-gray-200 → border-[var(--border-color)]"
208
+ echo " bg-blue-600 → bg-[var(--color-accent-500)]"
209
+ echo " text-blue-600 → text-[var(--color-accent-500)]"
210
+ echo " text-red-500 → text-[var(--error-text)]"
211
+ echo " bg-green-500 → bg-[var(--success-bg)]"
212
+ echo ""
213
+ echo "See references/smartstack-frontend.md section 4 for full variable reference"
214
+ echo ""
215
+ echo "$HARDCODED"
216
+ fi
217
+ fi
218
+
219
+ # POST-CHECK C11: Frontend routes must use kebab-case (BLOCKING)
220
+ APP_TSX=$(find src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
221
+ if [ -n "$APP_TSX" ]; then
222
+ FE_PATHS=$(grep -oP "path:\s*['\"]([^'\"]+)['\"]" "$APP_TSX" | grep -oP "['\"][^'\"]+['\"]" | tr -d "'" | tr -d '"')
223
+ for FE_PATH in $FE_PATHS; do
224
+ for SEG in $(echo "$FE_PATH" | tr '/' '\n'); do
225
+ echo "$SEG" | grep -qP '^:' && continue
226
+ if echo "$SEG" | grep -qP '^[a-z]{10,}$'; then
227
+ SEED_MATCH=$(echo "$SEED_ROUTES" | tr '/' '\n' | grep -P "^[a-z]+-[a-z]+" | tr -d '-' | grep -x "$SEG" || true)
228
+ if [ -n "$SEED_MATCH" ]; then
229
+ echo "BLOCKING: Frontend route segment '$SEG' appears to be missing hyphens"
230
+ echo "Seed data uses kebab-case (e.g., 'human-resources') but frontend has '$SEG'"
231
+ echo "Fix: Use kebab-case in App.tsx route paths to match seed data exactly"
232
+ FAIL=true
233
+ fi
234
+ fi
235
+ done
236
+ done
237
+ fi
238
+
239
+ # POST-CHECK C24: i18n must use separate JSON files per language (not embedded in index.ts)
240
+ TSX_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
241
+ if [ -n "$TSX_FILES" ]; then
242
+ if [ ! -d "src/i18n/locales" ]; then
243
+ echo "WARNING: Missing src/i18n/locales/ directory"
244
+ echo "Translations must be in separate JSON files: src/i18n/locales/{fr,en,it,de}/{module}.json"
245
+ echo "NEVER embed translations in src/i18n/index.ts or a single TypeScript file"
246
+ fi
247
+
248
+ I18N_INDEX=$(find src/i18n/ -maxdepth 1 -name "index.ts" -o -name "index.tsx" -o -name "config.ts" 2>/dev/null || true)
249
+ if [ -n "$I18N_INDEX" ]; then
250
+ EMBEDDED=$(grep -Pn '^\s*(resources|translations)\s*[:=]\s*\{' $I18N_INDEX 2>/dev/null || true)
251
+ if [ -n "$EMBEDDED" ]; then
252
+ echo "WARNING: Translations embedded in i18n config file — must be in separate JSON files"
253
+ echo "Found embedded translations in:"
254
+ echo "$EMBEDDED"
255
+ echo ""
256
+ echo "Fix: Move translations to src/i18n/locales/{fr,en,it,de}/{module}.json"
257
+ echo "The i18n config should import from locales/ directory, not contain inline translations"
258
+ fi
259
+ fi
260
+
261
+ for LANG in fr en it de; do
262
+ if [ ! -d "src/i18n/locales/$LANG" ]; then
263
+ echo "WARNING: Missing language directory: src/i18n/locales/$LANG/"
264
+ echo "SmartStack requires 4 languages: fr, en, it, de"
265
+ fi
266
+ done
267
+
268
+ for LANG in fr en it de; do
269
+ JSON_COUNT=$(find "src/i18n/locales/$LANG" -name "*.json" 2>/dev/null | wc -l)
270
+ if [ "$JSON_COUNT" -eq 0 ]; then
271
+ echo "WARNING: No translation JSON files in src/i18n/locales/$LANG/"
272
+ echo "Each module must have a {module}.json file per language"
273
+ fi
274
+ done
275
+ fi
276
+
277
+ # POST-CHECK C25: Pages must use useTranslation hook (no hardcoded user-facing strings)
278
+ PAGE_FILES=$(find src/pages/ -name "*.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
279
+ if [ -n "$PAGE_FILES" ]; then
280
+ TOTAL_PAGES=$(echo "$PAGE_FILES" | wc -l)
281
+ I18N_PAGES=$(grep -l "useTranslation" $PAGE_FILES 2>/dev/null | wc -l)
282
+ if [ "$TOTAL_PAGES" -gt 0 ] && [ "$I18N_PAGES" -eq 0 ]; then
283
+ echo "WARNING: No page files use useTranslation — all user-facing text must be translated"
284
+ echo "Found $TOTAL_PAGES page files but 0 use useTranslation"
285
+ echo ""
286
+ echo "Fix: Import and use useTranslation in every page component:"
287
+ echo " const { t } = useTranslation(['{module}']);"
288
+ echo " t('{module}:title', 'Fallback text')"
289
+ fi
290
+
291
+ HARDCODED_TEXT=$(grep -Pn '>\s*(Create|Edit|Delete|Save|Cancel|Search|Loading|Error|No data|Not found|Submit|Back|Actions|Name|Status|Description)\s*<' $PAGE_FILES 2>/dev/null | grep -v '{t(' | head -10 || true)
292
+ if [ -n "$HARDCODED_TEXT" ]; then
293
+ echo "WARNING: Possible hardcoded user-facing strings detected in JSX"
294
+ echo "All user-facing text MUST use t('namespace:key', 'Fallback')"
295
+ echo "$HARDCODED_TEXT"
296
+ fi
297
+ fi
298
+
299
+ # POST-CHECK C26: List/Detail pages must include DocToggleButton (documentation panel)
300
+ LIST_PAGES=$(find src/pages/ -name "*ListPage.tsx" -o -name "*sPage.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
301
+ if [ -n "$LIST_PAGES" ]; then
302
+ MISSING_DOC=0
303
+ for PAGE in $LIST_PAGES; do
304
+ if ! grep -q "DocToggleButton" "$PAGE" 2>/dev/null; then
305
+ echo "WARNING: Page missing DocToggleButton: $PAGE"
306
+ echo " Import: import { DocToggleButton } from '@/components/docs/DocToggleButton';"
307
+ echo " Place in header actions: <DocToggleButton />"
308
+ MISSING_DOC=$((MISSING_DOC + 1))
309
+ fi
310
+ done
311
+ if [ "$MISSING_DOC" -gt 0 ]; then
312
+ echo ""
313
+ echo "WARNING: $MISSING_DOC pages missing DocToggleButton — users cannot access inline documentation"
314
+ echo "See smartstack-frontend.md section 7 for placement pattern"
315
+ fi
316
+ fi
317
+
318
+ # POST-CHECK C27: Module documentation must be generated (doc-data.ts)
319
+ TSX_PAGES=$(find src/pages/ -name "*.tsx" -not -name "*.test.*" 2>/dev/null | grep -v node_modules | grep -v "docs/" || true)
320
+ DOC_DATA=$(find src/pages/docs/ -name "doc-data.ts" 2>/dev/null || true)
321
+ if [ -n "$TSX_PAGES" ] && [ -z "$DOC_DATA" ]; then
322
+ echo "WARNING: Frontend pages exist but no documentation generated"
323
+ echo "Fix: Invoke /documentation {module} --type user to generate doc-data.ts"
324
+ echo "The DocToggleButton in page headers will link to this documentation"
325
+ fi
326
+
327
+ # POST-CHECK C36: Frontend navigate() calls must have matching route definitions (CRITICAL)
328
+ PAGE_FILES=$(find web/ -name "*.tsx" -path "*/pages/*" ! -name "*.test.tsx" 2>/dev/null || true)
329
+ if [ -n "$PAGE_FILES" ]; then
330
+ NAV_TARGETS=$(grep -oP "navigate\(['\"]([^'\"]+)['\"]" $PAGE_FILES 2>/dev/null | grep -oP "['\"][^'\"]+['\"]" | tr -d "'" | tr -d '"' | sort -u || true)
331
+ APP_FILES=$(find web/ -name "App.tsx" -o -name "routes.tsx" -o -name "applicationRoutes*.tsx" -o -name "clientRoutes*.tsx" -o -name "componentRegistry.generated.ts" 2>/dev/null || true)
332
+ if [ -n "$APP_FILES" ] && [ -n "$NAV_TARGETS" ]; then
333
+ ROUTE_PATHS=$(grep -oP "path:\s*['\"]([^'\"]+)['\"]" $APP_FILES 2>/dev/null | grep -oP "['\"][^'\"]+['\"]" | tr -d "'" | tr -d '"' | sort -u || true)
334
+ for TARGET in $NAV_TARGETS; do
335
+ if echo "$TARGET" | grep -qP '^(:|/api|http|-[0-9])'; then continue; fi
336
+ LAST_SEG=$(echo "$TARGET" | grep -oP '[a-z][-a-z0-9]*$' || true)
337
+ if [ -z "$LAST_SEG" ]; then continue; fi
338
+ FOUND=$(echo "$ROUTE_PATHS" | grep -F "$LAST_SEG" 2>/dev/null || true)
339
+ if [ -z "$FOUND" ]; then
340
+ SEG_PASCAL=$(echo "$LAST_SEG" | sed -r 's/(^|-)([a-z])/\U\2/g')
341
+ PAGE_EXISTS=$(find web/ -name "${SEG_PASCAL}Page.tsx" -o -name "${SEG_PASCAL}ListPage.tsx" -o -name "${SEG_PASCAL}sPage.tsx" 2>/dev/null || true)
342
+ if [ -z "$PAGE_EXISTS" ]; then
343
+ SOURCE_FILE=$(grep -rl "navigate(['\"].*${LAST_SEG}" $PAGE_FILES 2>/dev/null | head -1 || true)
344
+ echo "CRITICAL: Dead link detected — navigate('$TARGET') in $SOURCE_FILE"
345
+ echo " Route segment '$LAST_SEG' has no matching route in App.tsx and no page component"
346
+ echo " Fix: Either create the page component + route, or remove the navigate() button"
347
+ FAIL=true
348
+ fi
349
+ fi
350
+ done
351
+ fi
352
+ fi
353
+
354
+ # POST-CHECK C37: Detail page tabs must NOT navigate() — content switches locally (CRITICAL)
355
+ DETAIL_PAGES=$(find src/ web/ -name "*DetailPage.tsx" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
356
+ if [ -n "$DETAIL_PAGES" ]; then
357
+ FAIL_C37=false
358
+ for DP in $DETAIL_PAGES; do
359
+ HAS_TABS=$(grep -P "useState.*activeTab|setActiveTab" "$DP" 2>/dev/null || true)
360
+ if [ -z "$HAS_TABS" ]; then continue; fi
361
+
362
+ TAB_NAVIGATE=$(grep -Pn "navigate\(" "$DP" 2>/dev/null | grep -v "navigate\(\s*['\"]edit['\"]" | grep -v "navigate\(\s*-1\s*\)" | grep -v "navigate\(\s*['\`].*/:id/edit" | grep -v "//" || true)
363
+ if [ -n "$TAB_NAVIGATE" ]; then
364
+ RELATIVE_NAV=$(echo "$TAB_NAVIGATE" | grep -P "navigate\(['\"\`]\.\./" 2>/dev/null || true)
365
+ if [ -n "$RELATIVE_NAV" ]; then
366
+ echo "CRITICAL: Detail page tabs use navigate() instead of local content switching: $DP"
367
+ echo " Tab click handlers MUST only call setActiveTab() — render content inline"
368
+ echo " Found navigate() calls (likely in tab handlers):"
369
+ echo "$RELATIVE_NAV"
370
+ echo ""
371
+ echo " Fix: Remove navigate() from tab handlers. Render sub-resource content inline:"
372
+ echo " {activeTab === 'leaves' && <LeaveRequestsTable employeeId={entity.id} />}"
373
+ echo " See smartstack-frontend.md section 3 'Tab Behavior Rules' for the correct pattern."
374
+ FAIL_C37=true
375
+ fi
376
+ fi
377
+ done
378
+ if [ "$FAIL_C37" = true ]; then
379
+ FAIL=true
380
+ fi
381
+ fi
382
+
383
+ # POST-CHECK C49: Route Ordering in App.tsx — static before dynamic (BLOCKING)
384
+ APP_TSX=$(find web/ src/ -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
385
+ if [ -n "$APP_TSX" ]; then
386
+ ROUTE_PATHS=$(grep -oP "path:\s*'([^']+)'" "$APP_TSX" | sed "s/path: '//;s/'//")
387
+ FAIL_C49=false
388
+ PREV_PREFIX=""
389
+ PREV_IS_DYNAMIC=false
390
+
391
+ while IFS= read -r ROUTE; do
392
+ [ -z "$ROUTE" ] && continue
393
+ PREFIX=$(echo "$ROUTE" | sed 's|/[^/]*$||')
394
+ LAST_SEGMENT=$(echo "$ROUTE" | grep -oP '[^/]+$')
395
+ IS_DYNAMIC=$(echo "$LAST_SEGMENT" | grep -c '^:')
396
+
397
+ if [ "$PREV_IS_DYNAMIC" = true ] && [ "$IS_DYNAMIC" -eq 0 ] && [ "$PREFIX" = "$PREV_PREFIX" ]; then
398
+ echo "BLOCKING: Static route '$ROUTE' is AFTER a dynamic route with prefix '$PREFIX' in App.tsx"
399
+ echo " React Router will match the dynamic route first → '$ROUTE' is unreachable (404)"
400
+ echo " Fix: Move static routes BEFORE dynamic routes within the same prefix group"
401
+ FAIL_C49=true
402
+ fi
403
+
404
+ PREV_PREFIX="$PREFIX"
405
+ if [ "$IS_DYNAMIC" -gt 0 ]; then
406
+ PREV_IS_DYNAMIC=true
407
+ else
408
+ PREV_IS_DYNAMIC=false
409
+ fi
410
+ done <<< "$ROUTE_PATHS"
411
+
412
+ if [ "$FAIL_C49" = true ]; then
413
+ FAIL=true
414
+ fi
415
+ fi
416
+
417
+ # POST-CHECK C52: Frontend route paths must include module segment (BLOCKING)
418
+ APP_TSX=$(find . -path "*/src/App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
419
+ if [ -n "$APP_TSX" ]; then
420
+ ROUTE_PATHS=$(grep -oP "path:\s*'[^']+'" "$APP_TSX" | grep -v "^path: ''" | grep -v ":id" | grep -v "create" | grep -v "edit" | sed "s/path: '//;s/'//")
421
+
422
+ NAV_SEED_FILES=$(find src/ -name "*NavigationSeedData.cs" -o -name "*NavigationModuleSeedData.cs" 2>/dev/null)
423
+ if [ -n "$NAV_SEED_FILES" ]; then
424
+ MODULE_CODES=$(grep -oP 'Code\s*=\s*"\K[^"]+' $NAV_SEED_FILES 2>/dev/null | sort -u || true)
425
+
426
+ for route in $ROUTE_PATHS; do
427
+ if [ -z "$route" ]; then continue; fi
428
+
429
+ if ! echo "$route" | grep -q "/"; then
430
+ IS_MODULE=false
431
+ for mod in $MODULE_CODES; do
432
+ if [ "$route" = "$mod" ]; then IS_MODULE=true; break; fi
433
+ done
434
+ if [ "$IS_MODULE" = false ]; then
435
+ echo "BLOCKING: Frontend route '$route' is missing module segment"
436
+ echo " Expected: '{module}/{route}' (e.g., 'employee-management/$route')"
437
+ echo " Backend navigation seed data defines routes with full hierarchy: /app/module/section"
438
+ echo " Frontend routes must match: module/section (relative to app root)"
439
+ FAIL=true
440
+ fi
441
+ fi
442
+ done
443
+ fi
444
+ fi
445
+
446
+ if [ "$FAIL" = true ]; then
447
+ exit 1
448
+ fi