@atlashub/smartstack-cli 4.75.0 → 4.79.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 (81) hide show
  1. package/dist/index.js +87 -41
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/project/claude-md/root.CLAUDE.md.template +1 -1
  5. package/templates/skills/ai-prompt/SKILL.md +64 -0
  6. package/templates/skills/ai-prompt/references/ai-agent-modes.md +89 -0
  7. package/templates/skills/ai-prompt/references/eval-framework.md +129 -0
  8. package/templates/skills/apex/SKILL.md +2 -2
  9. package/templates/skills/apex/references/checks/frontend-checks.sh +123 -11
  10. package/templates/skills/apex/references/checks/seed-checks.sh +81 -7
  11. package/templates/skills/apex/references/core-seed-data.md +27 -22
  12. package/templates/skills/apex/references/domain-events-pattern.md +45 -0
  13. package/templates/skills/apex/references/entity-hooks-pattern.md +68 -0
  14. package/templates/skills/apex/references/licensing-enforcement.md +52 -0
  15. package/templates/skills/apex/references/post-checks.md +18 -1
  16. package/templates/skills/apex/references/smartstack-api.md +116 -5
  17. package/templates/skills/apex/references/smartstack-frontend.md +1 -1
  18. package/templates/skills/apex/references/smartstack-layers.md +6 -6
  19. package/templates/skills/apex/steps/step-00-init.md +1 -1
  20. package/templates/skills/apex/steps/step-03b-layer1-seed.md +26 -0
  21. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +124 -2
  22. package/templates/skills/apex/steps/step-04-examine.md +163 -0
  23. package/templates/skills/apex-verify/SKILL.md +110 -0
  24. package/templates/skills/apex-verify/references/audit-rules.md +50 -0
  25. package/templates/skills/apex-verify/steps/step-00-init.md +119 -0
  26. package/templates/skills/apex-verify/steps/step-01-nav-audit.md +96 -0
  27. package/templates/skills/apex-verify/steps/step-02-crud-audit.md +127 -0
  28. package/templates/skills/apex-verify/steps/step-03-perm-audit.md +119 -0
  29. package/templates/skills/apex-verify/steps/step-04-route-audit.md +98 -0
  30. package/templates/skills/apex-verify/steps/step-05-report.md +110 -0
  31. package/templates/skills/application/references/contexts-cheatsheet.md +86 -0
  32. package/templates/skills/application/references/extensions-system.md +158 -0
  33. package/templates/skills/application/references/frontend-route-naming.md +7 -5
  34. package/templates/skills/application/references/frontend-verification.md +7 -5
  35. package/templates/skills/application/references/provider-template.md +4 -2
  36. package/templates/skills/application/references/smartstack-provider.md +118 -0
  37. package/templates/skills/application/references/themes-db-driven.md +484 -0
  38. package/templates/skills/application/templates-frontend.md +2 -2
  39. package/templates/skills/application/templates-seed.md +4 -2
  40. package/templates/skills/audit-route/references/routing-pattern.md +3 -1
  41. package/templates/skills/business-analyse/SKILL.md +3 -3
  42. package/templates/skills/business-analyse/_shared.md +37 -0
  43. package/templates/skills/business-analyse/react/components.md +30 -28
  44. package/templates/skills/business-analyse/references/03-json-schemas.md +11 -3
  45. package/templates/skills/business-analyse/references/03-post-check-validation.md +64 -0
  46. package/templates/skills/business-analyse/references/canonical-json-formats.md +7 -3
  47. package/templates/skills/business-analyse/references/robustness-checks.md +1 -1
  48. package/templates/skills/business-analyse/references/validation-checklist.md +5 -5
  49. package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +15 -4
  50. package/templates/skills/business-analyse/steps/step-03-specify.md +162 -4
  51. package/templates/skills/business-analyse/steps/step-04-consolidate.md +211 -1
  52. package/templates/skills/business-analyse/templates-react.md +15 -15
  53. package/templates/skills/business-analyse-handoff/references/agent-handoff-transform-prompt.md +3 -0
  54. package/templates/skills/business-analyse-html/html/ba-interactive.html +198 -16
  55. package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +64 -0
  56. package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +80 -11
  57. package/templates/skills/business-analyse-html/html/src/scripts/06-render-consolidation.js +2 -2
  58. package/templates/skills/business-analyse-html/html/src/scripts/06-render-mockups.js +6 -3
  59. package/templates/skills/business-analyse-html/html/src/scripts/12-render-diagrams.js +46 -0
  60. package/templates/skills/business-analyse-html/references/02-feature-data-building.md +4 -2
  61. package/templates/skills/business-analyse-html/references/data-build.md +2 -0
  62. package/templates/skills/business-analyse-html/references/data-mapping.md +88 -21
  63. package/templates/skills/business-analyse-html/steps/step-02-build-data.md +6 -0
  64. package/templates/skills/business-analyse-html/steps/step-04-verify.md +92 -3
  65. package/templates/skills/business-analyse-quick/SKILL.md +807 -0
  66. package/templates/skills/{sketch → business-analyse-quick}/references/domain-heuristics.md +59 -3
  67. package/templates/skills/business-analyse-quick/references/prd-schema.md +268 -0
  68. package/templates/skills/business-analyse-review/references/review-data-mapping.md +6 -0
  69. package/templates/skills/cli-app-sync/SKILL.md +105 -4
  70. package/templates/skills/cli-app-sync/references/comparison-map.md +13 -0
  71. package/templates/skills/cli-app-sync/references/diff-entities.md +162 -0
  72. package/templates/skills/dev-start/SKILL.md +7 -7
  73. package/templates/skills/documentation/templates.md +16 -16
  74. package/templates/skills/migrate/SKILL.md +312 -0
  75. package/templates/skills/migrate/references/v3.34-to-v3.46.md +289 -0
  76. package/templates/skills/sketch/SKILL.md +15 -153
  77. package/templates/skills/smoke-generation/SKILL.md +313 -0
  78. package/templates/skills/ui-components/SKILL.md +11 -1
  79. package/templates/skills/ui-components/patterns/data-table.md +1 -1
  80. package/templates/skills/ui-components/references/component-catalog.md +82 -0
  81. package/templates/skills/workflow/SKILL.md +70 -1
@@ -109,7 +109,6 @@ public static class NavigationApplicationSeedData
109
109
  return new NavigationApplicationSeedEntry
110
110
  {
111
111
  Id = ApplicationId,
112
- Zone = ApplicationZone.Business,
113
112
  Code = "{appCode}",
114
113
  Label = "{appLabel_en}",
115
114
  Description = "{appDesc_en}",
@@ -117,7 +116,9 @@ public static class NavigationApplicationSeedData
117
116
  IconType = IconType.Lucide,
118
117
  Route = ToKebabCase("/{appCode}"),
119
118
  DisplayOrder = 1,
120
- IsActive = true
119
+ IsActive = true,
120
+ IsOpen = false, // true => bypass permission checks (system apps only)
121
+ IsPersonal = false // true => belongs to user personal scope (myspace)
121
122
  };
122
123
  }
123
124
 
@@ -199,7 +200,6 @@ public static class NavigationApplicationSeedData
199
200
  public class NavigationApplicationSeedEntry
200
201
  {
201
202
  public Guid Id { get; init; }
202
- public ApplicationZone Zone { get; init; }
203
203
  public string Code { get; init; } = null!;
204
204
  public string Label { get; init; } = null!;
205
205
  public string? Description { get; init; }
@@ -208,6 +208,9 @@ public class NavigationApplicationSeedEntry
208
208
  public string? Route { get; init; }
209
209
  public int DisplayOrder { get; init; }
210
210
  public bool IsActive { get; init; }
211
+ // v3.46+ : ApplicationZone enum removed. Replaced by 2 boolean flags.
212
+ public bool IsOpen { get; init; } // true => app accessible without permission checks
213
+ public bool IsPersonal { get; init; } // true => app in user personal scope (myspace)
211
214
  }
212
215
  ```
213
216
 
@@ -404,12 +407,11 @@ public static readonly Guid {ResourcePascal}ResourceId = Guid.NewGuid();
404
407
 
405
408
  ### Section Methods (add to {ModulePascal}NavigationSeedData.cs)
406
409
 
407
- > **ROUTE SPECIAL CASES (list and detail):**
408
- > The `list` and `detail` sections are view modes of the module, NOT functional sub-areas.
409
- > - `list` section route = module route (e.g., `/human-resources/employees`) — NO `/list` suffix
410
- > - `detail` section route = module route + `/:id` (e.g., `/human-resources/employees/:id`) — NOT `/detail/:id`
411
- > - Do not use: `/{module}/list`, `/{module}/detail/:id`
412
- > - Other sections (dashboard, approve, import) = module route + `/{section-kebab}` (normal)
410
+ > **ROUTE CONVENTION:**
411
+ > ALL sections use a UNIFORM route pattern: module route + `/{section-code}`.
412
+ > - `list` section route = module route + `/list` (e.g., `/human-resources/employees/list`)
413
+ > - `detail` section route = module route + `/:id` (e.g., `/human-resources/employees/:id`) — NOT `/detail/:id` (detail is an implicit route, not a section)
414
+ > - Other sections (dashboard, approve, import) = module route + `/{section-kebab}` (same rule)
413
415
 
414
416
  ```csharp
415
417
  // --- Add AFTER GetTranslationEntries() in {ModulePascal}NavigationSeedData.cs ---
@@ -434,14 +436,18 @@ public static IEnumerable<NavigationSectionSeedEntry> GetSectionEntries(Guid mod
434
436
  Description = "{section1_desc_en}",
435
437
  Icon = "{section1_icon}",
436
438
  IconType = IconType.Lucide,
437
- // ROUTE CONVENTION:
438
- // - "list" section → same as module route (no extra segment)
439
- // - "detail" section → module route + "/:id"
440
- // - Other sections module route + "/{section-kebab}"
441
- // Do not use: "/employees/list", "/employees/detail/:id"
439
+ // ROUTE CONVENTION: ALL sections use module route + "/{section-code}" (uniform rule)
440
+ // - "list" section → module route + "/list" (e.g., /human-resources/employees/list)
441
+ // - Other sections → module route + "/{section-kebab}" (same rule)
442
+ // Implicit routes (detail, create, edit) are NOT sections registered by DynamicRouter convention
442
443
  Route = "{section_route}", // From seedDataCore.navigationSections[].route
443
444
  DisplayOrder = {section1_sort},
444
- IsActive = true
445
+ // VISIBILITY RULE (MANDATORY):
446
+ // Reserved codes ("detail", "create", "edit") and codes containing "-detail"
447
+ // are internal route targets, NOT sidebar menu items.
448
+ // → IsActive = false for these codes (DynamicRouter resolves them by convention)
449
+ // → IsActive = true for all other sections (visible in sidebar menu)
450
+ IsActive = true // Set to FALSE if section code is "detail", "create", "edit", or "*-detail"
445
451
  }
446
452
  // Repeat for each section...
447
453
  };
@@ -522,15 +528,14 @@ public static IEnumerable<NavigationResourceSeedEntry> GetResourceEntries(Guid s
522
528
  {
523
529
  // RESOURCE ROUTE CONVENTION:
524
530
  // Resources inherit their parent section's resolved route as base:
525
- // - Under "list" section → base = module route (no /list)
526
- // - Under "detail" section → base = module route (no /detail, resource routes don't include /:id)
531
+ // - Under "list" section → base = module route + /list
527
532
  // - Under other sections → base = module route + /{section-kebab}
528
533
  // Then append: /{resource-kebab}
529
534
  //
530
535
  // Example: resource "export" under section "dashboard":
531
536
  // Route = /human-resources/employees/dashboard/export
532
537
  // Example: resource "employees-grid" under section "list":
533
- // Route = /human-resources/employees/employees-grid (NOT /employees/list/employees-grid)
538
+ // Route = /human-resources/employees/list/employees-grid
534
539
  new NavigationResourceSeedEntry
535
540
  {
536
541
  Id = {Resource1Pascal}ResourceId,
@@ -539,7 +544,7 @@ public static IEnumerable<NavigationResourceSeedEntry> GetResourceEntries(Guid s
539
544
  Label = "{resource1_label_en}",
540
545
  EntityType = "{resource1_entity}",
541
546
  // Use parent section's resolved route + /{resource-kebab}
542
- // For "list"/"detail" sections, the section route = module route (no /list or /detail segment)
547
+ // For "list" sections, route = module route + /list. "detail" is implicit (not a section).
543
548
  Route = "{resource_route}", // From seedDataCore: parent section route + /{resource-kebab}
544
549
  DisplayOrder = 1
545
550
  }
@@ -591,10 +596,10 @@ public class NavigationResourceSeedEntry
591
596
  | `{section_label_xx}` | `specification.navigation.entries[]` where `level == "section"` → `labels.xx` |
592
597
  | `{section_icon}` | `seedDataCore.navigationSections[].icon` |
593
598
  | `{section_sort}` | `seedDataCore.navigationSections[].sort` |
594
- | `{section_route}` | `seedDataCore.navigationSections[].route` — **SPECIAL CASES:** `list` module route (no `/list`), `detail` → module route + `/:id` (no `/detail/:id`), others module route + `/{section-kebab}` |
599
+ | `{section_route}` | `seedDataCore.navigationSections[].route` — ALL sections use uniform rule: module route + `/{section-code}` (`list` → module route + `/list`). `detail` is implicit (not a section): module route + `/:id`. |
595
600
  | `{resourceCode}` | `seedDataCore.navigationResources[].code` |
596
601
  | `{resource_entity}` | `seedDataCore.navigationResources[].entity` |
597
- | `{resource_route}` | Computed from parent section route + `/{resource-kebab}`. **SPECIAL CASES:** if parent section is `list` → module route + `/{resource-kebab}` (no `/list/`), if parent is `detail` → module route + `/{resource-kebab}` (no `/detail/`). |
602
+ | `{resource_route}` | Computed from parent section route + `/{resource-kebab}`. For `list` parent → module route + `/list/{resource-kebab}`. For other parents → module route + `/{section-kebab}/{resource-kebab}`. |
598
603
  | `{parentSectionCode}` | `seedDataCore.navigationResources[].parentCode` |
599
604
 
600
605
  ---
@@ -1367,7 +1372,7 @@ Before marking the task as completed, verify ALL:
1367
1372
  **Application-Level (FIRST — before modules):**
1368
1373
  - [ ] `NavigationApplicationSeedData.cs` created (once per application, at `Infrastructure/Persistence/Seeding/Data/`)
1369
1374
  - [ ] Application GUID is random (`Guid.NewGuid()`) — FK resolution is by Code lookup, not fixed ID
1370
- - [ ] GetApplicationEntry() takes no parameters, includes `Zone = ApplicationZone.Business`
1375
+ - [ ] GetApplicationEntry() takes no parameters, includes `IsOpen = false` and `IsPersonal = false` (set `IsPersonal = true` only for myspace-scoped apps; set `IsOpen = true` only for public/system apps that should bypass permission checks). v3.46+ : `ApplicationZone` enum is removed — do NOT add `Zone = ...`
1371
1376
  - [ ] Application translations created (4 languages: fr, en, it, de, EntityType = Application), using `app.Id` (actual DB ID) for EntityId
1372
1377
  - [ ] `IClientSeedDataProvider.SeedNavigationAsync()` uses `NavigationApplicationSeedData` (NO hardcoded `{appLabel_en}` / `{appIcon}` placeholders)
1373
1378
  - [ ] `ApplicationRolesSeedData.ApplicationId` references `NavigationApplicationSeedData.ApplicationId` (DTO only — provider code resolves from DB by Code)
@@ -0,0 +1,45 @@
1
+ # Domain Events (lightweight pub-sub)
2
+
3
+ > Extracted from `smartstack-api.md` for clarity. Loaded only when generating code that needs events.
4
+
5
+ SmartStack uses a minimal domain event pattern in `SmartStack.Domain.Support.Events`.
6
+
7
+ ## Interface + base class
8
+
9
+ ```csharp
10
+ public interface IDomainEvent
11
+ {
12
+ DateTime OccurredAt { get; }
13
+ }
14
+
15
+ public abstract class DomainEvent : IDomainEvent
16
+ {
17
+ public DateTime OccurredAt { get; } = DateTime.UtcNow;
18
+ }
19
+ ```
20
+
21
+ ## When to raise an event vs use a hook
22
+
23
+ | Use case | Mechanism |
24
+ |---|---|
25
+ | Cross-cutting side effect tied to entity lifecycle (Create/Update/Delete) | Entity hook (`IAfterCreate<T>`, …) — see [entity-hooks-pattern.md](entity-hooks-pattern.md) |
26
+ | Business event independent of CRUD (e.g. `WorkflowExecuted`, `LicenseRenewed`) | Domain event |
27
+ | Need to handle multiple unrelated reactions to the same business fact | Domain event |
28
+ | Reaction must be transactional with the entity write | Neither — code it inline in the handler |
29
+
30
+ ## Convention
31
+
32
+ - One event class per business fact, named `{PastTense}Event` (e.g. `EmployeeOnboardedEvent`, `WorkflowExecutedEvent`).
33
+ - Place events in `Domain/{Aggregate}/Events/`.
34
+ - Inherit `DomainEvent` (gets `OccurredAt` for free).
35
+ - Dispatched by handlers (no automatic publication from `SaveChangesAsync` in v3.46) — keep dispatch explicit so the handler controls timing.
36
+
37
+ ## Example dispatch (handler)
38
+
39
+ ```csharp
40
+ await _mediator.Publish(new EmployeeOnboardedEvent(employee.Id, employee.TenantId), ct);
41
+ ```
42
+
43
+ ## Source
44
+
45
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Domain/Support/Events/IDomainEvent.cs`
@@ -0,0 +1,68 @@
1
+ # Entity Lifecycle Hooks (v3.46+)
2
+
3
+ > Extracted from `smartstack-api.md` for clarity. Loaded only when generating code that needs hooks.
4
+
5
+ SmartStack exposes 6 typed hook interfaces + 1 executor in `SmartStack.Application.Common.Interfaces.Hooks`.
6
+
7
+ ## Interfaces
8
+
9
+ ```csharp
10
+ public interface IBeforeCreate<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
11
+ public interface IAfterCreate<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
12
+ public interface IBeforeUpdate<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
13
+ public interface IAfterUpdate<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
14
+ public interface IBeforeDelete<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
15
+ public interface IAfterDelete<in T> where T : class { int Order => 0; Task ExecuteAsync(T entity, CancellationToken ct = default); }
16
+
17
+ public interface IHookExecutor
18
+ {
19
+ Task ExecuteBeforeCreateAsync<T>(T entity, CancellationToken ct = default) where T : class;
20
+ Task ExecuteAfterCreateAsync<T>(T entity, CancellationToken ct = default) where T : class;
21
+ Task ExecuteBeforeUpdateAsync<T>(T entity, CancellationToken ct = default) where T : class;
22
+ Task ExecuteAfterUpdateAsync<T>(T entity, CancellationToken ct = default) where T : class;
23
+ Task ExecuteBeforeDeleteAsync<T>(T entity, CancellationToken ct = default) where T : class;
24
+ Task ExecuteAfterDeleteAsync<T>(T entity, CancellationToken ct = default) where T : class;
25
+ }
26
+ ```
27
+
28
+ ## Semantics
29
+
30
+ - **`IBefore*`** runs **before** the entity is persisted. May transform the entity. May `throw` to cancel the operation (the calling handler does NOT call `SaveChangesAsync`).
31
+ - **`IAfter*`** runs **after** `SaveChangesAsync` completes. Side-effects only (notifications, indexing, caches, integrations). Throwing here does NOT roll back the DB write — wrap risky calls.
32
+ - **`Order`** (default 0) — lower runs earlier. Use `Order = -100` to pre-empt validation, `Order = 100` to run last.
33
+
34
+ ## DI registration (Infrastructure DependencyInjection.cs)
35
+
36
+ ```csharp
37
+ services.AddScoped<IHookExecutor, HookExecutor>();
38
+ services.AddScoped<IBeforeCreate<Employee>, EmployeeValidationHook>();
39
+ services.AddScoped<IAfterCreate<Employee>, EmployeeWelcomeNotificationHook>();
40
+ services.AddScoped<IAfterUpdate<Employee>, EmployeeUpdatedAuditHook>();
41
+ ```
42
+
43
+ ## Usage in service / handler
44
+
45
+ ```csharp
46
+ public async Task<EmployeeDto> CreateAsync(CreateEmployeeDto dto, CancellationToken ct)
47
+ {
48
+ var entity = Employee.Create(dto.TenantId, dto.Code, dto.Name);
49
+ await _hooks.ExecuteBeforeCreateAsync(entity, ct); // may throw → cancel
50
+ _db.Employees.Add(entity);
51
+ await _db.SaveChangesAsync(ct);
52
+ await _hooks.ExecuteAfterCreateAsync(entity, ct); // post-commit side effects
53
+ return _mapper.Map<EmployeeDto>(entity);
54
+ }
55
+ ```
56
+
57
+ ## DO / DON'T
58
+
59
+ - DO use hooks for cross-cutting concerns (notifications, audit beyond `IAuditableEntity`, cache invalidation)
60
+ - DO keep hooks small and idempotent — `IAfter*` runs without DB transaction
61
+ - DON'T use hooks for business validation that varies per command — keep that in the handler / FluentValidation
62
+ - DON'T register the same hook twice — DI picks all of them, and they all execute
63
+
64
+ ## Sources
65
+
66
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Application/Common/Interfaces/Hooks/IBeforeCreate.cs`
67
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Application/Common/Interfaces/Hooks/IAfterCreate.cs`
68
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Application/Common/Interfaces/Hooks/IHookExecutor.cs` (and 4 more)
@@ -0,0 +1,52 @@
1
+ # Licensing — `[RequiresLicense]` attribute (v3.46+)
2
+
3
+ > Extracted from `smartstack-api.md` for clarity. Loaded only when generating code that needs license gating.
4
+
5
+ Mark a Command or Query as requiring a valid license / feature / write capability.
6
+
7
+ ## Attribute definition
8
+
9
+ ```csharp
10
+ namespace SmartStack.Application.Common.Licensing;
11
+
12
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
13
+ public class RequiresLicenseAttribute : Attribute
14
+ {
15
+ public string? Feature { get; }
16
+ public bool IsWriteOperation { get; }
17
+
18
+ public RequiresLicenseAttribute() { /* just needs valid license */ }
19
+ public RequiresLicenseAttribute(bool isWriteOperation) { /* blocked in read-only mode */ }
20
+ public RequiresLicenseAttribute(string feature, bool isWriteOperation = false) { /* both */ }
21
+ }
22
+ ```
23
+
24
+ ## Usage on a Command
25
+
26
+ ```csharp
27
+ [RequiresLicense(LicenseFeatures.AdvancedWorkflows, isWriteOperation: true)]
28
+ public record CreateAdvancedWorkflowCommand(string Name, ...) : IRequest<WorkflowDto>;
29
+ ```
30
+
31
+ ## Behavior at runtime
32
+
33
+ - A MediatR pipeline behavior reads the attribute on the request type.
34
+ - `Feature` → checked against the cached `License.Features` (resolved per-tenant from JWT).
35
+ - `IsWriteOperation = true` → blocked when the system is in read-only mode (license expired but in grace period, license downgraded, …).
36
+ - Failures throw a typed exception → 403 Forbidden with a stable error code (`LICENSE_FEATURE_DISABLED`, `LICENSE_READ_ONLY`).
37
+
38
+ ## Where to read feature constants
39
+
40
+ `SmartStack.Domain.Licensing.LicenseFeatures` (string consts — `Entra`, `AdvancedWorkflows`, `AiAdvanced`, …).
41
+
42
+ ## DO / DON'T
43
+
44
+ - DO put `[RequiresLicense]` on the request (Command/Query), not on the handler
45
+ - DO use `LicenseFeatures.X` constants — never magic strings
46
+ - DON'T add `[RequiresLicense]` on `[AllowAnonymous]` endpoints — they bypass the pipeline
47
+ - DON'T duplicate the check in the handler body — the pipeline already enforced it
48
+
49
+ ## Sources
50
+
51
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Application/Common/Licensing/RequiresLicenseAttribute.cs`
52
+ - `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Domain/Licensing/LicenseFeatures.cs`
@@ -75,6 +75,7 @@ bash references/checks/infrastructure-checks.sh
75
75
  | C37 | CRITICAL | Detail page tabs must NOT navigate() — content switches locally | frontend-checks.sh |
76
76
  | C49 | BLOCKING | Route Ordering in App.tsx — static before dynamic | frontend-checks.sh |
77
77
  | C52 | BLOCKING | Frontend route paths must include module segment | frontend-checks.sh |
78
+ | C64 | BLOCKING | ListPages must have Create/New button (navigate to create route) | frontend-checks.sh |
78
79
 
79
80
  ### Seed Data — Navigation, Roles, Permissions (C1-C2, C10, C15-C23, C32-C35, C44-C48, C53)
80
81
 
@@ -89,7 +90,7 @@ bash references/checks/infrastructure-checks.sh
89
90
  | C18 | BLOCKING | Permissions.cs static constants must exist | seed-checks.sh |
90
91
  | C19 | BLOCKING | ApplicationRolesSeedData.cs must exist | seed-checks.sh |
91
92
  | C20 | WARNING | Section route completeness (NavigationSection → frontend route + permissions) | seed-checks.sh |
92
- | C21 | WARNING | FORBIDDEN route patterns — /list and /detail/:id | seed-checks.sh |
93
+ | C21 | WARNING | FORBIDDEN route patterns — /detail/:id | seed-checks.sh |
93
94
  | C22 | WARNING | Permission path segment count (2-4 dots expected) | seed-checks.sh |
94
95
  | C23 | BLOCKING | IClientSeedDataProvider must have 4 methods with real implementation (not stubs) + DI registration | seed-checks.sh |
95
96
  | C32 | CRITICAL | Translation seed data must have idempotency guard | seed-checks.sh |
@@ -104,6 +105,7 @@ bash references/checks/infrastructure-checks.sh
104
105
  | C53 | BLOCKING | Enum serialization — JsonStringEnumConverter required | seed-checks.sh |
105
106
  | C57 | BLOCKING | SeedDataProvider Seed*Async methods must NOT be `return Task.CompletedTask` or empty body — stubs cause empty menu | seed-checks.sh |
106
107
  | C58 | BLOCKING | File paths in filesToCreate must be valid C# identifiers — no spaces, apostrophes, or accents | seed-checks.sh |
108
+ | C63 | BLOCKING | Reserved section codes (detail/create/edit/*-detail) must NOT be seeded as menu items | seed-checks.sh |
107
109
 
108
110
  ### Architecture — Clean Architecture Layer Isolation (A1-A8)
109
111
 
@@ -135,6 +137,21 @@ bash references/checks/infrastructure-checks.sh
135
137
  | C60 | BLOCKING | Controllers must inject ISender _mediator, NOT direct business services | infrastructure-checks.sh |
136
138
  | C61 | BLOCKING | Controllers with [NavRoute] must have [Authorize] attribute | infrastructure-checks.sh |
137
139
 
140
+ ### PRD Compliance — Delegate Mode Only (PC-1 to PC-6)
141
+
142
+ > **These checks run ONLY in delegate mode** (when `/apex -d` is used with companion spec files).
143
+ > They validate that generated code matches the PRD specifications from `.ralph/` companion files.
144
+ > Implemented in `step-04-examine.md` section 6d — no separate .sh script needed.
145
+
146
+ | ID | Severity | Description | Source |
147
+ |----|----------|-------------|--------|
148
+ | PC-1 | BLOCKING | All columns from screens.json SmartTable resources must exist in generated ListPage | step-04 §6d |
149
+ | PC-2 | BLOCKING | All actions from screens.json (create, edit, delete, approve, reject, export) must have handlers in pages | step-04 §6d |
150
+ | PC-3 | WARNING | I18n labels must have correct UTF-8 accents (FR: Employés not Employes, DE: Übersicht not Ubersicht) | step-04 §6d |
151
+ | PC-4 | BLOCKING | All permissionPaths from permissions.json must exist in PermissionsSeedData.cs and Permissions.cs | step-04 §6d |
152
+ | PC-5 | WARNING | Every primary/functional section in screens.json must have a corresponding page file | step-04 §6d |
153
+ | PC-6 | WARNING | Module i18n namespace must be registered in i18n config (prevents raw key display) | step-04 §6d |
154
+
138
155
  ---
139
156
 
140
157
  ## Summary
@@ -16,10 +16,46 @@ public abstract class BaseEntity
16
16
  public Guid Id { get; set; }
17
17
  public DateTime CreatedAt { get; set; }
18
18
  public DateTime? UpdatedAt { get; set; }
19
+
20
+ // v3.46+ : JSON storage for SDK extension fields. Default "{}".
21
+ public string ExtensionData { get; private set; } = "{}";
19
22
  }
20
23
  ```
21
24
 
22
- **ONLY 3 properties.** No Code, no IsDeleted, no RowVersion, no SoftDelete, no CreatedBy/UpdatedBy.
25
+ **4 inherited properties** (v3.46+). No Code, no IsDeleted, no RowVersion, no SoftDelete, no CreatedBy/UpdatedBy. `Code` is a business property — add it on the concrete entity. `CreatedBy`/`UpdatedBy` come from `IAuditableEntity` (separate interface).
26
+
27
+ ### ExtensionData — SDK custom fields (v3.46+)
28
+
29
+ `BaseEntity.ExtensionData` is a JSON string allowing SDK clients to attach custom fields to ANY entity without schema migrations. The base class exposes 7 typed methods:
30
+
31
+ ```csharp
32
+ // Read a typed value
33
+ var color = entity.GetExtensionValue<string>("color");
34
+ var meta = entity.GetExtensionValue<MyMetadata>("meta");
35
+
36
+ // Write
37
+ entity.SetExtensionValue("color", "blue");
38
+ entity.SetExtensionValue("meta", new MyMetadata { Tag = "vip" });
39
+
40
+ // Inspect
41
+ if (entity.HasExtensionValue("color")) { ... }
42
+ var all = entity.GetAllExtensionData(); // IReadOnlyDictionary<string, JsonElement>
43
+
44
+ // Bulk replace
45
+ entity.SetAllExtensionData(new Dictionary<string, object?> { ["a"] = 1, ["b"] = 2 });
46
+
47
+ // Cleanup
48
+ entity.RemoveExtensionValue("color"); // returns bool
49
+ entity.ClearExtensionData(); // resets to "{}"
50
+ ```
51
+
52
+ **Conventions:**
53
+ - Serialization uses `JsonNamingPolicy.CamelCase` — `MyField` is stored as `"myField"`. Frontend consumers see camelCase.
54
+ - Mutating methods automatically update `UpdatedAt`.
55
+ - Backed by SQL Server `nvarchar(max)`. Map with `builder.Property(x => x.ExtensionData).HasColumnType("nvarchar(max)").HasDefaultValue("{}");` in EF config.
56
+ - `ExtensionData` is for **client SDK extensions** — NOT for storing application-controlled data (use real columns for those).
57
+
58
+ > Source : `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Domain/Common/BaseEntity.cs`
23
59
 
24
60
  ---
25
61
 
@@ -90,6 +126,38 @@ Verify in `Program.cs`: `JsonSerializerOptions.Converters.Add(new JsonStringEnum
90
126
 
91
127
  ---
92
128
 
129
+ ## File Storage — `StorageType` enum (v3.46+)
130
+
131
+ ```csharp
132
+ namespace SmartStack.Domain.Common;
133
+
134
+ public enum StorageType
135
+ {
136
+ Normal = 0, // Standard Azure Blob — deletable any time
137
+ Legal = 1 // Azure Legal Hold — immutable, 10-year retention (Swiss law Art. 958f CO)
138
+ }
139
+ ```
140
+
141
+ **When to use `Legal`:**
142
+ - Contracts, invoices, accounting documents (Swiss CO art. 958f → 10 years)
143
+ - Audit logs that must survive deletion attempts (compliance, GDPR Art. 30)
144
+ - Anything required by regulation to outlive the user's delete intent
145
+
146
+ **When `Normal` is enough:**
147
+ - User-uploaded avatars, attachments to tickets, message attachments, theme assets
148
+ - Anything the user owns and can legitimately delete
149
+
150
+ **Storage routing (Infrastructure):**
151
+ - `Normal` → standard container (deletable, free tier eligible)
152
+ - `Legal` → container with **Azure Blob immutable storage policy** + retention lock applied at upload
153
+ - The choice is made by the entity author when creating the file — not a runtime toggle
154
+
155
+ **AppSettings:** `appsettings.json` exposes both containers under `AzureStorage.Normal` and `AzureStorage.Legal` (with `EnableLegalHold` and `EnableRetentionPolicy` flags). Local dev uses `FileStorage.Normal/Legal` folders.
156
+
157
+ > Source : `D:/01 - projets/SmartStack.app/features/IA-Workflow/src/SmartStack.Domain/Common/StorageType.cs`
158
+
159
+ ---
160
+
93
161
  ## Entity Patterns
94
162
 
95
163
  ### Tenant-scoped (most common)
@@ -333,6 +401,49 @@ public class {Name}Controller : ControllerBase
333
401
 
334
402
  **Sub-resource completeness:** If a parent page has a navigate() to a sub-resource route, the frontend MUST include a page for that route. Otherwise → dead link → white screen. Prefer separate controllers (with Suffix) over sub-endpoints in parent controller.
335
403
 
404
+ ### Core Controllers Exception (Bootstrap / Navigation / Config)
405
+
406
+ A small set of **bootstrap controllers** uses `[Route("api/[controller]")]` instead of `[NavRoute]`. These are routed BEFORE the navigation registry is built from the DB, so they cannot use NavRoute.
407
+
408
+ | Controller | Route | Reason |
409
+ |---|---|---|
410
+ | `BootstrapController` | `api/bootstrap` | Returns initial config (CSRF token, public flags) |
411
+ | `NavigationController` | `api/navigation` | Returns the navigation menu — must run before NavRoute resolves |
412
+ | `ConfigController` (if present) | `api/config` | Public app config (read-only) |
413
+ | `AuthController` | `api/auth` | Login/refresh — runs before authenticated routes resolve |
414
+
415
+ **Rules for core controllers:**
416
+ - Use the classical `[Route("api/[controller]")]` attribute
417
+ - Still apply `[Authorize]` and `[RequirePermission]` when applicable (most are anonymous-allowed)
418
+ - Do NOT add to navigation seed data
419
+ - POST-CHECK ignores these specific controller names when validating NavRoute usage
420
+
421
+ **Do not extend this list arbitrarily.** Application/business controllers MUST use `[NavRoute]`. If you think a new controller belongs to the bootstrap set, justify it: the controller must run before navigation is loaded.
422
+
423
+ ---
424
+
425
+ ## Entity Lifecycle Hooks (v3.46+)
426
+
427
+ 6 typed hook interfaces + 1 executor in `SmartStack.Application.Common.Interfaces.Hooks` : `IBeforeCreate<T>`, `IAfterCreate<T>`, `IBeforeUpdate<T>`, `IAfterUpdate<T>`, `IBeforeDelete<T>`, `IAfterDelete<T>`, `IHookExecutor`. `IBefore*` may throw to cancel ; `IAfter*` runs post-commit (no rollback). Each has an `Order` property (default 0).
428
+
429
+ **See [entity-hooks-pattern.md](entity-hooks-pattern.md)** for full interface signatures, DI registration, usage example, and DO/DON'T list.
430
+
431
+ ---
432
+
433
+ ## Domain Events (lightweight pub-sub)
434
+
435
+ Minimal pattern in `SmartStack.Domain.Support.Events` : `IDomainEvent { OccurredAt }` + `DomainEvent` abstract base. Use **events** for business facts independent of CRUD ; use **hooks** for cross-cutting reactions tied to the entity lifecycle. Dispatch is explicit (no auto-publication from `SaveChangesAsync` in v3.46).
436
+
437
+ **See [domain-events-pattern.md](domain-events-pattern.md)** for the full when-to-use table, naming convention, and dispatch example.
438
+
439
+ ---
440
+
441
+ ## Licensing — `[RequiresLicense]` attribute (v3.46+)
442
+
443
+ Mark a Command or Query class as requiring a valid license, a specific feature (`LicenseFeatures.X`), or write capability (blocked in read-only mode). MediatR pipeline reads the attribute and throws 403 with stable error codes (`LICENSE_FEATURE_DISABLED`, `LICENSE_READ_ONLY`).
444
+
445
+ **See [licensing-enforcement.md](licensing-enforcement.md)** for the attribute definition (3 constructors), usage on Commands, runtime behavior, feature constants location, and DO/DON'T.
446
+
336
447
  ---
337
448
 
338
449
  ## Navigation Seed Data (routes must be full paths)
@@ -343,10 +454,10 @@ public class {Name}Controller : ControllerBase
343
454
  | Module | `/{app-kebab}/{module-kebab}` | `/human-resources/employees` |
344
455
  | Section | `/{app-kebab}/{module-kebab}/{section-kebab}` | `/human-resources/employees/departments` |
345
456
 
346
- **Route special cases:** `list` and `detail` sections are view modes, NOT sub-areas:
347
- - `list` route = module route (e.g., `/human-resources/employees`)
348
- - `detail` route = module route + `/:id`
349
- - Do NOT use: `/employees/list`, `/employees/detail/:id`
457
+ **Route convention:** ALL sections use uniform route = module route + `/{section-code}`:
458
+ - `list` route = module route + `/list` (e.g., `/human-resources/employees/list`)
459
+ - `detail` is an implicit route (not a section): module route + `/:id`
460
+ - Do NOT use: `/employees/detail/:id` (detail is implicit)
350
461
 
351
462
  **Rules:**
352
463
  - Routes ALWAYS start with `/`, include full hierarchy, use kebab-case
@@ -256,7 +256,7 @@ const applicationRoutes: ApplicationRouteExtensions = {
256
256
  };
257
257
  ```
258
258
 
259
- **Section-level routes:** `list` and `detail` do NOT add path segments (handled by `index: true` and `:id`). Only other sections (dashboard, approve, import) add `{section-kebab}` child routes.
259
+ **Section-level routes:** ALL sections add their code as path segment (`list` `/list`, `dashboard` → `/dashboard`, `approve` `/approve`). `detail`/`create`/`edit` are implicit routes (`/:id`, `/create`, `/:id/edit`), NOT sections.
260
260
 
261
261
  **PermissionGuard for sections:**
262
262
  ```tsx
@@ -175,12 +175,12 @@ var sectionRoute = $"{moduleRoute}/{ToKebabCase(sectionCode)}";
175
175
  // → "/human-resources/employees/departments"
176
176
  ```
177
177
 
178
- **Route special cases (list and detail sections):**
179
- > `list` and `detail` are view modes of the module, NOT functional sub-areas.
180
- > - `list` section route = module route (e.g., `/human-resources/employees`) — NO `/list` suffix
181
- > - `detail` section route = module route + `/:id` (e.g., `/human-resources/employees/:id`) — NOT `/detail/:id`
182
- > - Do not use: `/employees/list`, `/employees/detail/:id`
183
- > - Other sections (dashboard, approve, import) = module route + `/{section-kebab}` (normal)
178
+ **Route convention:**
179
+ > ALL sections use a UNIFORM route pattern: module route + `/{section-code}`.
180
+ > - `list` section route = module route + `/list` (e.g., `/human-resources/employees/list`)
181
+ > - `detail` is an implicit route (not a section): module route + `/:id` (e.g., `/human-resources/employees/:id`)
182
+ > - Other sections (dashboard, approve, import) = module route + `/{section-kebab}` (same rule)
183
+ > - Do NOT use: `/employees/detail/:id` (detail is implicit, not a section code)
184
184
 
185
185
  **Do not:**
186
186
  - Use deterministic/sequential/fixed GUIDs — always use `Guid.NewGuid()`
@@ -164,7 +164,7 @@ IF precision_score < 2:
164
164
  ║ Your request looks more like a discovery/design task. ║
165
165
  ║ ║
166
166
  ║ Suggestions: ║
167
- ║ 1. Use /sketch to quickly design your module (~2 min)
167
+ ║ 1. Use /business-analyse-quick to design your module
168
168
  ║ 2. Use /business-analyse for full analysis (40+ questions) ║
169
169
  ║ 3. Rewrite your prompt with entities and fields, e.g.: ║
170
170
  ║ /apex add employees section with Employee entity ║
@@ -100,6 +100,32 @@ IF existing_seed_files is NOT EMPTY:
100
100
 
101
101
  ---
102
102
 
103
+ ### Reserved Section Code Guard (MANDATORY — runs before ANY section seeding)
104
+
105
+ > **CRITICAL RULE:** Reserved section codes must NOT be seeded as menu-visible sections.
106
+
107
+ ```
108
+ RESERVED_SECTION_CODES = ["detail", "create", "edit"]
109
+
110
+ For each section in {sections} (from PRD or entity_section_map):
111
+ IF section.code IN RESERVED_SECTION_CODES OR section.code contains "-detail":
112
+ → Do NOT seed this section in NavigationSeedData at all
113
+ → "detail" is the /:id route of its parent list section (DynamicRouter convention)
114
+ → "create" is the /create route (DynamicRouter convention)
115
+ → "edit" is the /:id/edit route (DynamicRouter convention)
116
+ → "*-detail" (e.g., "department-detail") is the /:id route of its parent section
117
+ → Set permissionMode = "inherit" (no dedicated permissions)
118
+ → LOG: "Skipped reserved section '{section.code}' — resolved by DynamicRouter convention"
119
+
120
+ ELSE:
121
+ → Seed normally with IsActive = true
122
+
123
+ In delegate mode (-d): PRD data may contain reserved codes that were not challenged.
124
+ This guard catches them regardless of mode.
125
+ ```
126
+
127
+ ---
128
+
103
129
  ### Per-Module Seed Data (CREATE or UPDATE)
104
130
 
105
131
  #### If SEED_MODE = "CREATE"