@atlashub/smartstack-cli 4.26.0 → 4.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.
Files changed (27) hide show
  1. package/dist/mcp-entry.mjs +33 -11
  2. package/dist/mcp-entry.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/agents/ba-writer.md +46 -46
  5. package/templates/project/appsettings.json.template +4 -6
  6. package/templates/skills/apex/SKILL.md +1 -0
  7. package/templates/skills/apex/references/challenge-questions.md +17 -0
  8. package/templates/skills/apex/references/core-seed-data.md +27 -4
  9. package/templates/skills/apex/references/post-checks.md +330 -0
  10. package/templates/skills/apex/references/smartstack-layers.md +31 -0
  11. package/templates/skills/apex/steps/step-02-plan.md +9 -0
  12. package/templates/skills/apex/steps/step-03-execute.md +102 -4
  13. package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +33 -0
  14. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +17 -0
  15. package/templates/skills/business-analyse/references/spec-auto-inference.md +12 -7
  16. package/templates/skills/business-analyse/steps/step-00-init.md +19 -9
  17. package/templates/skills/business-analyse/steps/step-02-structure.md +20 -6
  18. package/templates/skills/business-analyse/steps/step-03-specify.md +7 -0
  19. package/templates/skills/business-analyse/steps/step-04-consolidate.md +2 -14
  20. package/templates/skills/controller/references/mcp-scaffold-workflow.md +20 -0
  21. package/templates/skills/derive-prd/references/handoff-file-templates.md +25 -1
  22. package/templates/skills/derive-prd/references/handoff-seeddata-generation.md +3 -1
  23. package/templates/skills/ralph-loop/references/category-completeness.md +125 -0
  24. package/templates/skills/ralph-loop/references/compact-loop.md +90 -3
  25. package/templates/skills/ralph-loop/references/module-transition.md +60 -0
  26. package/templates/skills/ralph-loop/steps/step-04-check.md +207 -12
  27. package/templates/skills/ralph-loop/steps/step-05-report.md +205 -14
@@ -257,6 +257,8 @@ var sectionRoute = $"{moduleRoute}/{ToKebabCase(sectionCode)}";
257
257
  - POST-CHECK C34 detects non-kebab-case NavRoute values. POST-CHECK C35 detects non-kebab-case permissions
258
258
  - Do not combine `[Route("api/...")]` with `[NavRoute]` — causes 404s (POST-CHECK C43)
259
259
  - `[NavRoute]` is the only route attribute needed — resolves routes from DB at startup
260
+ - **NavRoute uniqueness:** Each controller MUST have a unique NavRoute value. Two controllers sharing the same NavRoute causes routing conflicts and 404s (POST-CHECK C50)
261
+ - **NavRoute segment count vs hierarchy:** If a controller lives in a section subfolder (e.g., `Controllers/{App}/Employees/ContractsController.cs`), its NavRoute MUST have 3 segments (`app.module.section`), not 2. A 2-segment NavRoute on a section controller causes API 404s because the resolved route doesn't match the expected path (POST-CHECK C51)
260
262
  - Do not use `[Authorize]` without specific permission
261
263
  - Swagger XML documentation
262
264
  - Return DTOs, never domain entities
@@ -307,12 +309,41 @@ const [loading, setLoading] = useState(true);
307
309
 
308
310
  **Loader:** `Loader2` from `lucide-react` for spinners, `PageLoader` for Suspense fallback
309
311
 
312
+ ### RULE — Frontend Route Ordering
313
+
314
+ > **CRITICAL:** React Router matches routes top-to-bottom. Dynamic routes (`:id`, `:id/edit`) placed BEFORE static routes (`dashboard`, `create`) will swallow the static paths — e.g., `/employees/dashboard` matches `:id` with `id="dashboard"` → 404.
315
+
316
+ **Order (MANDATORY):**
317
+
318
+ 1. Index/list route (path: `''` or `'{section}'`)
319
+ 2. Create route (`'{section}/create'`)
320
+ 3. Static sections (`'{section}/dashboard'`, `'{section}/departments'`, etc.)
321
+ 4. Dynamic routes (`'{section}/:id'`, `'{section}/:id/edit'`)
322
+ 5. Redirect routes (`Navigate`) — ALWAYS LAST
323
+
324
+ ```tsx
325
+ // ✅ CORRECT — static before dynamic
326
+ { path: 'employees', element: <EmployeesPage /> },
327
+ { path: 'employees/create', element: <CreateEmployeePage /> },
328
+ { path: 'employees/dashboard', element: <EmployeeDashboardPage /> },
329
+ { path: 'employees/:id', element: <EmployeeDetailPage /> },
330
+ { path: 'employees/:id/edit', element: <EditEmployeePage /> },
331
+ { path: '', element: <Navigate to="employees" replace /> },
332
+
333
+ // ❌ WRONG — :id before dashboard → dashboard is unreachable (matched as id="dashboard")
334
+ { path: 'employees/:id', element: <EmployeeDetailPage /> },
335
+ { path: 'employees/dashboard', element: <EmployeeDashboardPage /> }, // NEVER REACHED
336
+ ```
337
+
338
+ POST-CHECK C49 detects this anti-pattern and BLOCKS.
339
+
310
340
  ### Section Routes (when module has sections)
311
341
 
312
342
  If the module defines `{sections}`, generate frontend routes for EACH section as children of the module route:
313
343
 
314
344
  ```tsx
315
345
  // Section routes are nested inside the module's children array
346
+ // IMPORTANT: static routes BEFORE dynamic routes (see Route Ordering rule above)
316
347
  { path: '{section-kebab}', element: <Suspense fallback={<PageLoader />}><{Section}Page /></Suspense> },
317
348
  { path: '{section-kebab}/create', element: <Suspense fallback={<PageLoader />}><Create{Section}Page /></Suspense> },
318
349
  { path: '{section-kebab}/:id', element: <Suspense fallback={<PageLoader />}><{Section}DetailPage /></Suspense> },
@@ -108,6 +108,15 @@ Verify the plan respects dependencies:
108
108
  - Build gate between EVERY layer (must pass): Layer 0 → 1 → 2 → 3 → 4
109
109
  ```
110
110
 
111
+ ### 4b. Module Code Collision Guard (BLOCKING)
112
+
113
+ ```
114
+ IF appCode == moduleCode:
115
+ BLOCK — module_code identique à app_code cause des segments doublés (e.g. /hr/hr).
116
+ Suggestion : "{appCode}-core", "{appCode}-management"
117
+ ASK user pour un module_code différent.
118
+ ```
119
+
111
120
  ---
112
121
 
113
122
  ## 5. Estimated Commits
@@ -290,6 +290,57 @@ When launching agents for multi-entity layers:
290
290
  - Agent principal updates activeForm after each entity completion:
291
291
  `TaskUpdate(taskId: layer2_task_id, activeForm: "Building {EntityName} backend (2/5 entities)")`
292
292
 
293
+ ### Controller NavRoute Attribute (MANDATORY)
294
+
295
+ After controller generation, verify `[NavRoute]` attribute is present on every controller:
296
+ - Expected: `[NavRoute("{app_name}.{module_code}.{section_code}")]` on the controller class
297
+ - If missing: Add it manually above `[Authorize]`
298
+ - When calling `scaffold_extension(type: "controller")`, always pass `navRoute` in options
299
+ - This is REQUIRED for `scaffold_routes` to auto-detect routes in Layer 3
300
+
301
+ ### Guard: NavRoute Uniqueness and Segment Count (MANDATORY)
302
+
303
+ **BEFORE proceeding past Layer 2**, verify for EACH controller:
304
+
305
+ 1. **Unique NavRoute:** No two controllers may share the same `[NavRoute("...")]` value. Duplicate NavRoutes cause routing conflicts → 404s on one of the controllers.
306
+
307
+ 2. **Segment count matches hierarchy:** Count the dots in the NavRoute value:
308
+ - 1 dot = 2 segments (module-level, e.g., `human-resources.employees`) — controller is at `Controllers/{App}/`
309
+ - 2 dots = 3 segments (section-level, e.g., `human-resources.employees.contracts`) — controller is at `Controllers/{App}/{Module}/` or in a section subfolder
310
+ - **If a controller is in a section subfolder** (e.g., `Controllers/{App}/Employees/ContractsController.cs`) **but has only 2 segments** → the API route will be wrong → 404. It MUST have 3 segments.
311
+ - 0 dots = INVALID → BLOCK
312
+
313
+ ```bash
314
+ # Quick validation
315
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
316
+ for f in $CTRL_FILES; do
317
+ NAVROUTE=$(grep -oP '\[NavRoute\("\K[^"]+' "$f")
318
+ if [ -n "$NAVROUTE" ]; then
319
+ DOTS=$(echo "$NAVROUTE" | tr -cd '.' | wc -c)
320
+ if [ "$DOTS" -eq 0 ]; then
321
+ echo "BLOCKING: NavRoute '$NAVROUTE' has only 1 segment (need minimum 2): $f"
322
+ exit 1
323
+ fi
324
+ # Check if controller is in a section subfolder but NavRoute has only 2 segments
325
+ DEPTH=$(echo "$f" | grep -oP 'Controllers/[^/]+/[^/]+/' | wc -l)
326
+ if [ "$DEPTH" -gt 0 ] && [ "$DOTS" -eq 1 ]; then
327
+ echo "WARNING: Controller in section subfolder but NavRoute has only 2 segments: $f"
328
+ echo " NavRoute: $NAVROUTE — expected 3 segments (app.module.section)"
329
+ fi
330
+ fi
331
+ done
332
+ ```
333
+
334
+ ```bash
335
+ # Quick check: all controllers must have [NavRoute] (not just [Route])
336
+ CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
337
+ for f in $CTRL_FILES; do
338
+ if ! grep -q "\[NavRoute" "$f" && grep -q "\[Route" "$f"; then
339
+ echo "WARNING: $f has [Route] but no [NavRoute] — add [NavRoute] for route auto-detection"
340
+ fi
341
+ done
342
+ ```
343
+
293
344
  ### Post-Layer 2 Build Gate
294
345
 
295
346
  ```bash
@@ -368,16 +419,31 @@ TaskUpdate(taskId: progress_tracker_id,
368
419
 
369
420
  For each module:
370
421
  - API client: MCP scaffold_api_client
371
- - Routes: MCP scaffold_routes (outputFormat: 'applicationRoutes') → generates lazy imports + Suspense
422
+ - Routes TWO mandatory steps:
423
+ 1. Generate registry: MCP scaffold_routes (source: 'controllers', outputFormat: 'applicationRoutes', dryRun: false)
424
+ → Creates navRoutes.generated.ts (required by POST-CHECK C2)
425
+ 2. Wire to App.tsx (see below)
372
426
  - Wire Routes to App.tsx: After scaffold_routes, routes must be wired into App.tsx:
373
427
  → Read App.tsx and detect the routing pattern
374
428
  → Pattern A (`applicationRoutes: ApplicationRouteExtensions`): add routes to `applicationRoutes['{application_kebab}'][]` with RELATIVE paths
375
429
  → Pattern B (JSX `<Route>`): nest routes inside `<Route path="/{application}" element={<AppLayout />}>` + duplicate in tenant block
430
+ → **BEFORE wiring:** Verify route ordering — static routes (`create`, `dashboard`, `departments`) MUST come BEFORE dynamic routes (`:id`, `:id/edit`). Redirect routes (`Navigate`) MUST be LAST. See `references/smartstack-layers.md` "RULE — Frontend Route Ordering".
376
431
  → Do not add business routes to `clientRoutes[]` — it is only for non-app routes (`/about`, `/pricing`)
377
432
  → All business applications use `<AppLayout />` as layout wrapper
378
433
  → See `references/frontend-route-wiring-app-tsx.md` for full Pattern A/B detection and examples
379
434
  → Verify: `mcp__smartstack__validate_frontend_routes (scope: 'routes')`
380
- - Pages: use /ui-components skill follow smartstack-frontend.md patterns:
435
+ - Pages per section: When a section exists (e.g., "list"), generate ALL 4 page types:
436
+ → {Section}ListPage.tsx (index route)
437
+ → {Section}DetailPage.tsx (/:id route) — click on list item navigates here
438
+ → Create{Section}Page.tsx (/create route)
439
+ → Edit{Section}Page.tsx (/:id/edit route)
440
+ "detail" is NEVER a separate section — it's the /:id route of the list section.
441
+ - Pages: **MANDATORY — INVOKE Skill("ui-components")** for ALL page generation.
442
+ ⚠ BLOCKING REQUIREMENT: You MUST call Skill("ui-components") for EVERY page (.tsx).
443
+ NEVER generate .tsx page code directly. NEVER write page content in Agent prompts.
444
+ NEVER use Write tool to create pages without first calling Skill("ui-components").
445
+ The skill loads the full style guide + patterns (entity-card, data-table, dashboard-chart, grid-layout).
446
+ Follow smartstack-frontend.md patterns:
381
447
  → React.lazy() for all page imports (named export wrapping)
382
448
  → `<Suspense fallback={<PageLoader />}>` around all lazy components
383
449
  → Page structure: hooks → useEffect(load) → loading → error → content
@@ -451,7 +517,7 @@ IF NOT economy_mode AND entities.length > 1:
451
517
  prompt='Execute Layer 3 frontend for {EntityName}:
452
518
  **MANDATORY: Read references/smartstack-frontend.md FIRST**
453
519
  - API client: MCP scaffold_api_client
454
- - Routes: MCP scaffold_routes (outputFormat: "applicationRoutes")
520
+ - Routes: MCP scaffold_routes (outputFormat: "applicationRoutes", dryRun: false) → MUST generate navRoutes.generated.ts
455
521
  - Wire Routes to App.tsx (BLOCKING): detect Pattern A/B, wire accordingly
456
522
  → See references/frontend-route-wiring-app-tsx.md for full patterns
457
523
  → Verify: mcp__smartstack__validate_frontend_routes (scope: "routes")
@@ -466,7 +532,21 @@ IF NOT economy_mode AND entities.length > 1:
466
532
  # All agents launched in parallel
467
533
 
468
534
  ELSE:
469
- # Agent principal handles all entities sequentially
535
+ # Economy mode: Agent principal handles all entities SEQUENTIALLY.
536
+ # MANDATORY: You MUST still call Skill("ui-components") for page generation.
537
+ # economy_mode only disables parallel agents — it does NOT skip /ui-components.
538
+ For each entity (sequentially):
539
+ 1. MCP scaffold_api_client
540
+ 2. MCP scaffold_routes (outputFormat: 'applicationRoutes')
541
+ 3. Wire routes to App.tsx (Pattern A/B — see references/frontend-route-wiring-app-tsx.md)
542
+ 4. **INVOKE Skill("ui-components")** — pass entity context:
543
+ - Entity: {EntityName}, Module: {ModuleName}, App: {AppName}
544
+ - Page types: List, Detail, Create, Edit (+ Dashboard if applicable)
545
+ - "CSS: Use CSS variables ONLY — bg-[var(--bg-card)], text-[var(--text-primary)]"
546
+ - "Forms: Create/Edit are FULL PAGES with own routes (/create, /:id/edit)"
547
+ - "I18n: ALL text uses t('namespace:key', 'Fallback')"
548
+ 5. I18n: 4 JSON files (fr, en, it, de) + register namespace in i18n config
549
+ 6. Form tests: co-located .test.tsx for Create and Edit pages
470
550
  ```
471
551
 
472
552
  ### Parallel Agents + TaskCreate Integration
@@ -495,6 +575,24 @@ When delegating to `/ui-components` skill, include explicit instructions:
495
575
  - "Forms: Create/Edit forms are FULL PAGES with own routes (e.g., `/create`, `/:id/edit`)."
496
576
  - "I18n: ALL text must use `t('namespace:key', 'Fallback')`. Generate JSON files in `src/i18n/locales/`."
497
577
 
578
+ ### HARD RULE — /ui-components is NON-NEGOTIABLE
579
+
580
+ > **VIOLATION CHECK:** If ANY .tsx page file was created by Write tool WITHOUT
581
+ > a prior Skill("ui-components") call in this execution, the frontend layer is INVALID.
582
+ >
583
+ > **You MUST NOT:**
584
+ > - Generate .tsx page code in Agent prompts and dispatch to Snipper agents
585
+ > - Write page content directly via the Write tool
586
+ > - Copy-paste page templates from your knowledge
587
+ >
588
+ > **You MUST:**
589
+ > - Call Skill("ui-components") which loads the style guide, responsive guidelines,
590
+ > accessibility rules, and pattern files (entity-card, data-table, dashboard-chart, grid-layout, kanban)
591
+ > - Let /ui-components generate all pages with correct conventions
592
+ >
593
+ > **Why this matters:** Without the skill, pages miss CSS variables, DataTable/EntityCard,
594
+ > memo()/useCallback, responsive mobile-first design, and accessibility patterns.
595
+
498
596
  ### Frontend Tests Inline
499
597
 
500
598
  > **Tests are scaffolded and run WITHIN Layer 3, not deferred to step-07.**
@@ -69,6 +69,39 @@ const applicationRoutes: ApplicationRouteExtensions = {
69
69
 
70
70
  Routes are automatically injected into BOTH standard (`/{application}/...`) and tenant-prefixed (`/t/:slug/{application}/...`) route trees by `mergeRoutes()`. No manual duplication needed.
71
71
 
72
+ #### RULE — Route Ordering in applicationRoutes
73
+
74
+ > **CRITICAL:** Routes within each application key MUST follow static-before-dynamic order.
75
+ > React Router matches top-to-bottom — a `:id` route placed before a `dashboard` route
76
+ > will match `dashboard` as an `id` parameter → 404.
77
+
78
+ ```tsx
79
+ const applicationRoutes: ApplicationRouteExtensions = {
80
+ 'human-resources': [
81
+ // ✅ CORRECT ORDER — static before dynamic
82
+ { path: 'employees', element: <EmployeesPage /> },
83
+ { path: 'employees/create', element: <CreateEmployeePage /> },
84
+ { path: 'employees/dashboard', element: <EmployeeDashboardPage /> },
85
+ { path: 'employees/:id', element: <EmployeeDetailPage /> },
86
+ { path: 'employees/:id/edit', element: <EditEmployeePage /> },
87
+
88
+ // Redirect routes ALWAYS LAST
89
+ { path: '', element: <Navigate to="employees" replace /> },
90
+ ],
91
+ };
92
+ ```
93
+
94
+ ```tsx
95
+ // ❌ FORBIDDEN — :id before static routes
96
+ 'human-resources': [
97
+ { path: 'employees/:id', element: <EmployeeDetailPage /> }, // ← WRONG: catches 'dashboard'
98
+ { path: 'employees/dashboard', element: <DashboardPage /> }, // ← NEVER REACHED
99
+ ]
100
+ ```
101
+
102
+ See `smartstack-layers.md` "RULE — Frontend Route Ordering" for the full ordering specification.
103
+ POST-CHECK C49 detects and BLOCKS this anti-pattern.
104
+
72
105
  #### Custom Applications (Pattern A)
73
106
 
74
107
  Custom application keys (any key **not** in the built-in list: `administration`, `support`, `user`, `api`) are fully supported in `applicationRoutes`. `mergeRoutes()` automatically:
@@ -89,6 +89,21 @@ FOR each entity in analysis.entities[]:
89
89
  IF endpoints.length === 0 -> ERROR: "Entity {entity.name} has NO endpoints — missing controller"
90
90
  ```
91
91
 
92
+ ## H. Permission Granularity Check
93
+
94
+ > Validates that section permission modes are respected in the generated permission data.
95
+
96
+ ```
97
+ FOR each module:
98
+ FOR each section in anticipatedSections[]:
99
+ IF section.sectionType == "view" OR section.permissionMode == "inherit":
100
+ IF section has own permissions in seedDataCore.permissions[] -> WARNING: "Section '{code}' is type 'view' but has own permissions — should inherit from module"
101
+ IF section.permissionMode == "read-only":
102
+ IF section has create/update/delete permissions -> ERROR: "Section '{code}' is read-only but has write permissions"
103
+ IF section.sectionType == "primary" AND NOT section.permissionMode:
104
+ -> WARNING: "Primary section '{code}' missing explicit permissionMode — defaulting to 'crud'"
105
+ ```
106
+
92
107
  ## Result Aggregation
93
108
 
94
109
  ```json
@@ -100,6 +115,8 @@ FOR each entity in analysis.entities[]:
100
115
  { "check": "id-uniqueness", "module": "...", "status": "PASS|ERROR", "details": "..." },
101
116
  { "check": "wireframe-layout", "module": "...", "status": "PASS|ERROR", "details": "..." },
102
117
  { "check": "seeddata-translations", "module": "...", "status": "PASS|ERROR", "details": "..." }
118
+ ,
119
+ { "check": "permission-granularity", "module": "...", "status": "PASS|WARNING|ERROR", "details": "..." }
103
120
  ]
104
121
  }
105
122
  ```
@@ -34,13 +34,18 @@
34
34
 
35
35
  > **RULE:** Sections = functional zones only. `create`/`edit` are separate pages with own URL routes (`/create` and `/:id/edit`). `detail` is a tabbed page reached from `list`.
36
36
 
37
- | featureType | Sections (functional zones) | List page includes | Form pages | Detail page tabs |
38
- |---|---|---|---|---|
39
- | data-centric | list | grid, filters, create button | `/create` page, `/:id/edit` page | Infos, {relations} |
40
- | workflow | list, approve | grid, filters, create button | `/create` page, `/:id/edit` page | Infos, {relations}, Historique |
41
- | integration | list | grid, filters, config button | `/create` page, `/:id/edit` page | Infos, Config, Logs |
42
- | reporting | dashboard | — | — | — |
43
- | full-module | list, dashboard | grid, filters, create button | `/create` page, `/:id/edit` page | Infos, {relations}, Historique |
37
+ | featureType | Sections (functional zones) | permissionMode | List page includes | Form pages | Detail page tabs |
38
+ |---|---|---|---|---|---|
39
+ | data-centric | list | `crud` | grid, filters, create button | `/create` page, `/:id/edit` page | Infos, {relations} |
40
+ | data-centric | (detail implicit) | `inherit` | | | |
41
+ | workflow | list | `crud` | grid, filters, create button | `/create` page, `/:id/edit` page | Infos, {relations}, Historique |
42
+ | workflow | approve | `custom:approve,reject` | — | — | — |
43
+ | integration | list | `crud` | grid, filters, config button | `/create` page, `/:id/edit` page | Infos, Config, Logs |
44
+ | reporting | dashboard | `read-only` | — | — | — |
45
+ | full-module | list | `crud` | grid, filters, create button | `/create` page, `/:id/edit` page | Infos, {relations}, Historique |
46
+ | full-module | dashboard | `read-only` | — | — | — |
47
+
48
+ > **RULE:** `detail` is NEVER a section with its own permission set. It is always `sectionType: view`, `permissionMode: inherit`. Detail pages inherit permissions from their parent module.
44
49
 
45
50
  ## Component Generation Rules
46
51
 
@@ -357,13 +357,12 @@ IF workflow_mode = "project":
357
357
  })
358
358
 
359
359
  Output path: docs/business-analyse/v{version}/index.json
360
- Thematic files (empty, initialized): docs/business-analyse/v{version}/*.json
360
+ Thematic files — EXACTLY these 3, NO OTHERS:
361
361
  - cadrage.json
362
362
  - validation.json
363
363
  - consolidation.json
364
- // NOTE: entities, rules, usecases, permissions, screens are MODULE-LEVEL only.
365
- // They are created by step-03 (specify) in each module directory.
366
- // Do NOT create them at project/app level — empty files mislead downstream tools.
364
+ FORBIDDEN: entities.json, rules.json, usecases.json, permissions.json, screens.json, handoff.json
365
+ Creating any FORBIDDEN file at project level is a BLOCKING ERROR.
367
366
 
368
367
  Store:
369
368
  project_id: string // PROJ-NNN
@@ -394,18 +393,29 @@ ELSE:
394
393
  })
395
394
 
396
395
  Output path: docs/{app}/business-analyse/v{version}/index.json
397
- Thematic files (empty, initialized): docs/{app}/business-analyse/v{version}/*.json
396
+ Thematic files — EXACTLY these 3, NO OTHERS:
398
397
  - cadrage.json
399
398
  - validation.json
400
- - handoff.json
401
- // NOTE: entities, rules, usecases, permissions, screens are MODULE-LEVEL only.
402
- // They are created by step-03 (specify) in each module directory.
403
- // Do NOT create them at app level — empty files mislead derive-prd and ralph-loop.
399
+ - consolidation.json
400
+ FORBIDDEN: entities.json, rules.json, usecases.json, permissions.json, screens.json, handoff.json
401
+ Creating any FORBIDDEN file at application level is a BLOCKING ERROR.
404
402
  ```
405
403
 
406
404
  > **Note:** In project mode, per-application index.json files are created later in step-01-cadrage.
407
405
  > In single-app mode, step-02 (structure) determines if it's single or multi-module.
408
406
 
407
+ ### POST-CREATE VERIFICATION (MANDATORY)
408
+
409
+ After creating thematic files, verify no forbidden files were created:
410
+
411
+ ```
412
+ List all *.json files in {docs_dir}/
413
+ Expected (project/application): [index.json, cadrage.json, validation.json, consolidation.json]
414
+ If any file NOT in expected list is found → DELETE it immediately + log WARNING
415
+ ```
416
+
417
+ This guard catches any accidental creation of module-only files at the wrong scope.
418
+
409
419
  ## Step 10: Update Config
410
420
 
411
421
  Update `.business-analyse/config.json` with new feature information.
@@ -72,16 +72,27 @@ For each module, anticipate the navigation structure:
72
72
  For each section:
73
73
  - code (kebab-case, e.g., "list", "detail", "dashboard")
74
74
  - label (display name)
75
+ - sectionType: primary | functional | view | embedded
76
+ - permissionMode: crud | custom | read-only | inherit
75
77
  - resources: [
76
78
  { code, type (SmartTable|SmartForm|SmartCard|SmartKanban|SmartDashboard|SmartFilter), label }
77
79
  ]
78
80
  ```
79
81
 
82
+ **Section classification rules:**
83
+
84
+ | sectionType | permissionMode | When to use | Examples |
85
+ |---|---|---|---|
86
+ | `primary` | `crud` | Main entry point of the module, visible in menu | list |
87
+ | `functional` | `crud` or `custom` | Independent functional zone with own access control | approve, import, planning |
88
+ | `view` | `inherit` | Subordinate view reached from a primary section | detail, edit |
89
+ | `embedded` | `read-only` | Widget or tab embedded in another section | dashboard (when embedded in module) |
90
+
80
91
  Common patterns:
81
- - **Data-centric module**: list (SmartTable) + detail (SmartForm)
82
- - **Workflow module**: list + detail + kanban (SmartKanban)
83
- - **Reporting module**: dashboard (SmartDashboard) + detail
84
- - **Full module**: list + detail + dashboard
92
+ - **Data-centric module**: list (`primary`/`crud`) + detail (`view`/`inherit`)
93
+ - **Workflow module**: list (`primary`/`crud`) + detail (`view`/`inherit`) + approve (`functional`/`custom`)
94
+ - **Reporting module**: dashboard (`primary`/`read-only`) + detail (`view`/`inherit`)
95
+ - **Full module**: list (`primary`/`crud`) + detail (`view`/`inherit`) + dashboard (`embedded`/`read-only`)
85
96
 
86
97
  ### 4. Dependency Graph
87
98
 
@@ -111,6 +122,9 @@ For EACH identified element, ask yourself:
111
122
  - Does this module need a dashboard?
112
123
  - Is the list/detail pattern sufficient or are there other views?
113
124
  - Are there workflow steps that need dedicated sections?
125
+ - Does this section need its own access control? If not → `view` or `embedded`
126
+ - Is this a read-only view (dashboard, balances, statistics)? If yes → `permissionMode: read-only`
127
+ - Is this section reached by clicking a row in another section? If yes → always `view`
114
128
 
115
129
  **Resources:**
116
130
  - Is SmartTable the right component for this list?
@@ -159,8 +173,8 @@ Write via ba-writer:
159
173
  "priority": "must",
160
174
  "entities": ["Employee", "Contract"],
161
175
  "anticipatedSections": [
162
- { "code": "list", "label": "Liste", "resources": [{ "code": "employees-grid", "type": "SmartTable" }] },
163
- { "code": "detail", "label": "Fiche", "resources": [{ "code": "employee-form", "type": "SmartForm" }] }
176
+ { "code": "list", "label": "Liste", "sectionType": "primary", "permissionMode": "crud", "resources": [{ "code": "employees-grid", "type": "SmartTable" }] },
177
+ { "code": "detail", "label": "Fiche", "sectionType": "view", "permissionMode": "inherit", "resources": [{ "code": "employee-form", "type": "SmartForm" }] }
164
178
  ]
165
179
  }
166
180
  ],
@@ -123,6 +123,10 @@ Define the permission matrix:
123
123
  }
124
124
  ```
125
125
 
126
+ **Auto-detection rules:**
127
+ - If the cadrage mentions "export", "Excel", "CSV", or "télécharger" → automatically add `.export` permission and an export use case (UC-{PREFIX}-EXPORT)
128
+ - If the cadrage mentions "import", "importer", "upload" → automatically add `.import` permission and an import use case (UC-{PREFIX}-IMPORT)
129
+
126
130
  ### F. Interface Specs — Delegated to /ba-design-ui
127
131
 
128
132
  > **Screen specifications are NOT produced in this step.**
@@ -163,6 +167,9 @@ Before advancing to step 04, verify:
163
167
  - [ ] All entity relationships reference existing entities
164
168
  - [ ] All UC business rule references exist
165
169
  - [ ] All permission paths follow the convention
170
+ - [ ] Sections with `sectionType: view` do NOT appear in `permissionPaths` (they inherit from module)
171
+ - [ ] Sections with `permissionMode: read-only` only have `.read` in `permissionPaths` (no create/update/delete)
172
+ - [ ] Sections with `permissionMode: inherit` have ZERO entries in `permissionPaths`
166
173
 
167
174
  ## Transition
168
175
 
@@ -423,20 +423,8 @@ ba-writer.enrichSection({
423
423
  // Update status
424
424
  ba-writer.updateStatus({feature_id}, "consolidated");
425
425
 
426
- // FIX H3: Clean up app-level index.json files entries
427
- // Only keep entries for files that actually have content at app level.
428
- // Module-level data lives in module directories — do NOT declare empty app-level files.
429
- ba-writer.enrichSection({
430
- featureId: {feature_id},
431
- section: "files",
432
- data: {
433
- // Keep only files that exist and have content at app level
434
- cadrage: { path: "cadrage.json", hash: "framed", lastModified: now() },
435
- validation: { path: "validation.json", hash: "consolidated", lastModified: now() }
436
- // REMOVED: entities, rules, usecases, permissions, screens, handoff
437
- // These exist only at module level (flat-file architecture)
438
- }
439
- });
426
+ // Clean up any forbidden files that may have been created at app level
427
+ ba-writer.cleanupAppLevelFiles({ featureId: {feature_id} });
440
428
 
441
429
  // Save workflow state for resume support
442
430
  ba-writer.enrichSection({
@@ -123,6 +123,26 @@ if (actualNavRoute !== permission_path) {
123
123
  console.warn(` Got: "${actualNavRoute}"`);
124
124
  // Warning only — proceed
125
125
  }
126
+
127
+ // Validate segment count
128
+ const segments = actualNavRoute ? actualNavRoute.split('.').length : 0;
129
+ if (segments < 2) {
130
+ console.error(`BLOCKING: NavRoute "${actualNavRoute}" has only ${segments} segment(s) — minimum 2 required`);
131
+ console.error(` Format: "app.module" (2 segments) or "app.module.section" (3 segments)`);
132
+ STOP;
133
+ }
134
+
135
+ // If entity is in a section subfolder, NavRoute must have 3+ segments
136
+ // Check: controller path has 3+ levels after Controllers/ → section-level
137
+ const controllerDir = controllerCall.controllerFile.replace(/[^/]*$/, '');
138
+ const depthAfterControllers = controllerDir.split('Controllers/')[1]?.split('/').filter(Boolean).length || 0;
139
+ if (depthAfterControllers >= 2 && segments < 3) {
140
+ console.warn(`WARNING: Controller is in a section subfolder (depth=${depthAfterControllers})`);
141
+ console.warn(` but NavRoute "${actualNavRoute}" has only ${segments} segments`);
142
+ console.warn(` Expected 3 segments: "app.module.section"`);
143
+ console.warn(` A 2-segment NavRoute on a section controller causes API 404s`);
144
+ // Warning — developer should verify and fix
145
+ }
126
146
  ```
127
147
 
128
148
  ---
@@ -30,6 +30,12 @@ From `usecases.json > useCases[]`:
30
30
 
31
31
  Include: Service per UC cluster, DTOs for API contracts, Validators (FluentValidation), Query handlers
32
32
 
33
+ **Validator generation rules:**
34
+ - Every entity with Create and/or Update use cases MUST have a corresponding Validator
35
+ - Validators MUST be registered in DI (`services.AddScoped<IValidator<CreateXxxDto>, CreateXxxValidator>()`)
36
+ - Validators MUST be injected into controllers/services that handle POST/PUT operations
37
+ - NO TODO/placeholder comments allowed in Validators — all validation rules from business rules (BR-VAL-*) must be implemented
38
+
33
39
  ## 4.3 Infrastructure Files
34
40
 
35
41
  From `entities.json > entities[]`:
@@ -48,7 +54,8 @@ Generated from `usecases.json` + `entities.json`:
48
54
 
49
55
  ```json
50
56
  "api": [
51
- { "path": "src/API/Controllers/{ApplicationName}/{EntityName}Controller.cs", "type": "ApiController", "linkedUCs": [], "linkedFRs": [], "module": "{moduleCode}" }
57
+ { "path": "src/API/Controllers/{ApplicationName}/{EntityName}Controller.cs", "type": "ApiController", "navRoute": "{app-kebab}.{module-kebab}", "isSection": false, "linkedUCs": [], "linkedFRs": [], "module": "{moduleCode}" },
58
+ { "path": "src/API/Controllers/{ApplicationName}/{ModuleName}/{SectionEntityName}Controller.cs", "type": "ApiController", "navRoute": "{app-kebab}.{module-kebab}.{section-kebab}", "isSection": true, "linkedUCs": [], "linkedFRs": [], "module": "{moduleCode}" }
52
59
  ]
53
60
  ```
54
61
 
@@ -80,6 +87,23 @@ From `screens.json > screens[]` and `usecases.json > useCases[]`:
80
87
 
81
88
  **Dashboard acceptance criteria:** Chart library (Recharts), chart types matching spec, KPI cards, filters, CSS variables, responsive layout, wireframe-matching positions.
82
89
 
90
+ ## 4.5b Notification Files (CONDITIONAL)
91
+
92
+ > Generated only when `lifeCycles[].transitions[].effects[]` contains entries with `type: "notification"`.
93
+
94
+ ```json
95
+ "notifications": [
96
+ { "path": "src/Application/Notifications/{ApplicationName}/{ModuleName}/{NotificationName}Notification.cs", "type": "Notification", "linkedUCs": [], "module": "{moduleCode}", "description": "Notification triggered by lifecycle transition" },
97
+ { "path": "src/Application/Notifications/{ApplicationName}/{ModuleName}/{NotificationName}NotificationHandler.cs", "type": "NotificationHandler", "linkedUCs": [], "module": "{moduleCode}", "description": "Handler that sends in-app/email notification" }
98
+ ]
99
+ ```
100
+
101
+ **Generation rules:**
102
+ - One Notification + Handler pair per unique `notification` effect in lifecycle transitions
103
+ - NotificationName derived from transition: `{Entity}{TransitionName}` (e.g., `OrderApproved`)
104
+ - Handler must use `INotificationService` for in-app and `IEmailService` for email type
105
+ - Notification must include: recipient resolution, template reference, and payload mapping
106
+
83
107
  ## 4.6 SeedData Files
84
108
 
85
109
  **OBLIGATORY: 2 app-level CORE + per module CORE (NavigationModule + NavigationSections + Permissions + Roles) + business per module:**
@@ -127,7 +127,9 @@ const seedDataCore = {
127
127
  ? `/${toKebabCase(appCode)}/${toKebabCase(m.code)}/:id`
128
128
  : `/${toKebabCase(appCode)}/${toKebabCase(m.code)}/${toKebabCase(s.code)}`,
129
129
  displayOrder: (j + 1) * 10,
130
- navigation: s.code === "detail" ? "hidden" : "visible"
130
+ navigation: s.code === "detail" ? "hidden" : "visible",
131
+ // Propagate permissionMode for section-level permission generation
132
+ permissionMode: s.permissionMode || (s.code === "detail" ? "inherit" : s.code === "dashboard" ? "read-only" : "crud")
131
133
  }))
132
134
  );
133
135