@atlashub/smartstack-cli 3.22.0 → 3.24.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 (34) hide show
  1. package/dist/mcp-entry.mjs +143 -174
  2. package/dist/mcp-entry.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/mcp-scaffolding/component.tsx.hbs +21 -1
  5. package/templates/skills/apex/SKILL.md +21 -0
  6. package/templates/skills/apex/references/smartstack-api.md +507 -0
  7. package/templates/skills/apex/references/smartstack-frontend.md +1081 -0
  8. package/templates/skills/apex/references/smartstack-layers.md +166 -20
  9. package/templates/skills/apex/steps/step-00-init.md +27 -14
  10. package/templates/skills/apex/steps/step-01-analyze.md +45 -3
  11. package/templates/skills/apex/steps/step-02-plan.md +5 -1
  12. package/templates/skills/apex/steps/step-03-execute.md +51 -9
  13. package/templates/skills/apex/steps/step-04-validate.md +251 -0
  14. package/templates/skills/apex/steps/step-05-examine.md +7 -0
  15. package/templates/skills/apex/steps/step-07-tests.md +48 -5
  16. package/templates/skills/business-analyse/_shared.md +6 -6
  17. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +1 -1
  18. package/templates/skills/business-analyse/questionnaire/07-ui.md +3 -3
  19. package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +1 -1
  20. package/templates/skills/business-analyse/references/entity-architecture-decision.md +3 -3
  21. package/templates/skills/business-analyse/references/handoff-file-templates.md +13 -5
  22. package/templates/skills/business-analyse/references/spec-auto-inference.md +14 -14
  23. package/templates/skills/business-analyse/steps/step-01-cadrage.md +2 -2
  24. package/templates/skills/business-analyse/steps/step-02-decomposition.md +1 -1
  25. package/templates/skills/business-analyse/steps/step-03a1-setup.md +2 -2
  26. package/templates/skills/business-analyse/steps/step-03b-ui.md +2 -1
  27. package/templates/skills/business-analyse/steps/step-05a-handoff.md +15 -4
  28. package/templates/skills/business-analyse/templates/tpl-frd.md +2 -2
  29. package/templates/skills/business-analyse/templates-frd.md +2 -2
  30. package/templates/skills/ralph-loop/references/category-rules.md +45 -7
  31. package/templates/skills/ralph-loop/references/compact-loop.md +2 -2
  32. package/templates/skills/ralph-loop/references/core-seed-data.md +10 -0
  33. package/templates/skills/ralph-loop/steps/step-02-execute.md +110 -1
  34. package/templates/skills/validate-feature/steps/step-05-db-validation.md +86 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "3.22.0",
3
+ "version": "3.24.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -174,7 +174,27 @@ export const {{name}}: React.FC<{{name}}Props> = ({
174
174
 
175
175
  {/* Form */}
176
176
  <form onSubmit={handleSubmit} className="p-6 space-y-6">
177
- {/* TODO: Add form fields */}
177
+ {/*
178
+ TODO: Add form fields based on entity properties.
179
+ IMPORTANT — Field type mapping:
180
+ - string properties → <input type="text" />
181
+ - bool properties → <input type="checkbox" />
182
+ - number properties → <input type="number" />
183
+ - DateTime properties → <input type="date" />
184
+ - Guid FK properties (e.g., EmployeeId, DepartmentId) → <EntityLookup /> (NEVER plain text input!)
185
+
186
+ For FK fields, use EntityLookup from @/components/ui/EntityLookup:
187
+ <EntityLookup
188
+ apiEndpoint="/api/{related-entity-route}"
189
+ value={data.relatedEntityId}
190
+ onChange={(id) => handleChange('relatedEntityId', id)}
191
+ label="Related Entity"
192
+ mapOption={(item) => ({ id: item.id, label: item.name, sublabel: item.code })}
193
+ required
194
+ />
195
+
196
+ See smartstack-frontend.md section 6 for the full EntityLookup pattern.
197
+ */}
178
198
  <div className="text-[var(--text-secondary)] text-center py-8">
179
199
  Add your form fields here
180
200
  </div>
@@ -91,6 +91,7 @@ Execute incremental SmartStack development using the APEX methodology. This skil
91
91
 
92
92
  | File | Purpose | Loaded by |
93
93
  |------|---------|-----------|
94
+ | `references/smartstack-api.md` | BaseEntity, interfaces, entity/config/controller patterns | step-01, step-03 |
94
95
  | `references/smartstack-layers.md` | Layer execution rules, skill/MCP mapping, seed data | step-02, step-03 |
95
96
  | `references/agent-teams-protocol.md` | TeamCreate, coordination, shutdown protocol | step-01, step-03 |
96
97
  </reference_files>
@@ -108,6 +109,26 @@ Execute incremental SmartStack development using the APEX methodology. This skil
108
109
 
109
110
  </execution_rules>
110
111
 
112
+ <error_handling>
113
+
114
+ ## Error Handling Strategy
115
+
116
+ | Error | Action | Max Retries |
117
+ |-------|--------|-------------|
118
+ | `dotnet build` fails | Identify error, fix via skill/MCP, rebuild | 3 |
119
+ | `npm run typecheck` fails | Fix TypeScript error, retry | 3 |
120
+ | MCP unavailable | Degraded mode: use `smartstack-api.md` as sole reference, no MCP validation | — |
121
+ | File lock (MSB3021) | Auto-use `--output /tmp/{project}_build` for build verification | — |
122
+ | NuGet restore required | Run `dotnet restore` before first `dotnet build` | 1 |
123
+ | Migration fails | Rollback migration (`dotnet ef migrations remove`), fix entity/config, retry | 2 |
124
+ | MCP scaffold output wrong | Verify against `smartstack-api.md` patterns, fix manually | — |
125
+ | POST-CHECK fails | Return to step-03, fix the issue, re-validate | 2 |
126
+ | Stuck after max retries | AskUserQuestion with options: "Try alternative", "Skip", "Discuss" | — |
127
+
128
+ **Principle:** Always fix CODE, never bypass checks. If stuck, escalate to user.
129
+
130
+ </error_handling>
131
+
111
132
  <success_criteria>
112
133
  - SmartStack context detected (context/app/module)
113
134
  - Plan validated with skill/MCP mapped for each file
@@ -0,0 +1,507 @@
1
+ # SmartStack Domain API Reference
2
+
3
+ > **Source of truth:** `SmartStack.app/src/SmartStack.Domain/Common/`
4
+ > **Loaded by:** step-01 (analyze), step-03 (execute)
5
+
6
+ ---
7
+
8
+ ## BaseEntity
9
+
10
+ ```csharp
11
+ namespace SmartStack.Domain.Common;
12
+
13
+ public abstract class BaseEntity
14
+ {
15
+ public Guid Id { get; set; }
16
+ public DateTime CreatedAt { get; set; }
17
+ public DateTime? UpdatedAt { get; set; }
18
+ }
19
+ ```
20
+
21
+ **ONLY 3 properties.** No Code, no IsDeleted, no RowVersion, no SoftDelete, no CreatedBy/UpdatedBy.
22
+
23
+ ---
24
+
25
+ ## Interfaces
26
+
27
+ ### ITenantEntity (mandatory tenant isolation)
28
+
29
+ ```csharp
30
+ public interface ITenantEntity
31
+ {
32
+ Guid TenantId { get; }
33
+ }
34
+ ```
35
+
36
+ ### IAuditableEntity (audit trail)
37
+
38
+ ```csharp
39
+ public interface IAuditableEntity
40
+ {
41
+ string? CreatedBy { get; set; }
42
+ string? UpdatedBy { get; set; }
43
+ }
44
+ ```
45
+
46
+ ### IOptionalTenantEntity (nullable tenant)
47
+
48
+ ```csharp
49
+ public interface IOptionalTenantEntity
50
+ {
51
+ Guid? TenantId { get; }
52
+ }
53
+ ```
54
+
55
+ ### IScopedTenantEntity (tenant + scope visibility)
56
+
57
+ ```csharp
58
+ public interface IScopedTenantEntity : IOptionalTenantEntity
59
+ {
60
+ EntityScope Scope { get; }
61
+ }
62
+ ```
63
+
64
+ ### EntityScope enum
65
+
66
+ ```csharp
67
+ public enum EntityScope
68
+ {
69
+ Tenant = 0, // Visible only to specific tenant (TenantId required)
70
+ Shared = 1, // Visible to all tenants (TenantId null)
71
+ Platform = 2 // Visible only to platform admins (HasGlobalAccess)
72
+ }
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Entity Pattern (tenant-scoped, most common)
78
+
79
+ ```csharp
80
+ using SmartStack.Domain.Common;
81
+
82
+ namespace {ProjectName}.Domain.Entities.{Context}.{App}.{Module};
83
+
84
+ public class {Name} : BaseEntity, ITenantEntity, IAuditableEntity
85
+ {
86
+ // ITenantEntity
87
+ public Guid TenantId { get; private set; }
88
+
89
+ // IAuditableEntity
90
+ public string? CreatedBy { get; set; }
91
+ public string? UpdatedBy { get; set; }
92
+
93
+ // Business properties (add your own)
94
+ public string Code { get; private set; } = null!;
95
+ public string Name { get; private set; } = null!;
96
+ public string? Description { get; private set; }
97
+ public bool IsActive { get; private set; } = true;
98
+
99
+ private {Name}() { }
100
+
101
+ public static {Name} Create(Guid tenantId, string code, string name)
102
+ {
103
+ if (tenantId == Guid.Empty)
104
+ throw new ArgumentException("TenantId is required", nameof(tenantId));
105
+
106
+ return new {Name}
107
+ {
108
+ Id = Guid.NewGuid(),
109
+ TenantId = tenantId,
110
+ Code = code.ToLowerInvariant(),
111
+ Name = name,
112
+ CreatedAt = DateTime.UtcNow
113
+ };
114
+ }
115
+
116
+ public void Update(string name, string? description)
117
+ {
118
+ Name = name;
119
+ Description = description;
120
+ UpdatedAt = DateTime.UtcNow;
121
+ }
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Entity Pattern (platform-level, no tenant)
128
+
129
+ ```csharp
130
+ public class {Name} : BaseEntity, IAuditableEntity
131
+ {
132
+ public string? CreatedBy { get; set; }
133
+ public string? UpdatedBy { get; set; }
134
+
135
+ // Business properties
136
+ public string Code { get; private set; } = null!;
137
+ public string Name { get; private set; } = null!;
138
+
139
+ private {Name}() { }
140
+
141
+ public static {Name} Create(string code, string name)
142
+ {
143
+ return new {Name}
144
+ {
145
+ Id = Guid.NewGuid(),
146
+ Code = code.ToLowerInvariant(),
147
+ Name = name,
148
+ CreatedAt = DateTime.UtcNow
149
+ };
150
+ }
151
+ }
152
+ ```
153
+
154
+ ---
155
+
156
+ ## EF Configuration Pattern
157
+
158
+ ```csharp
159
+ using Microsoft.EntityFrameworkCore;
160
+ using Microsoft.EntityFrameworkCore.Metadata.Builders;
161
+
162
+ public class {Name}Configuration : IEntityTypeConfiguration<{Name}>
163
+ {
164
+ public void Configure(EntityTypeBuilder<{Name}> builder)
165
+ {
166
+ builder.ToTable("{prefix}{Name}s", "{schema}");
167
+
168
+ builder.HasKey(x => x.Id);
169
+
170
+ // Tenant (if ITenantEntity)
171
+ builder.Property(x => x.TenantId).IsRequired();
172
+ builder.HasIndex(x => x.TenantId)
173
+ .HasDatabaseName("IX_{prefix}{Name}s_TenantId");
174
+
175
+ // Business properties
176
+ builder.Property(x => x.Code).HasMaxLength(50).IsRequired();
177
+ builder.Property(x => x.Name).HasMaxLength(100).IsRequired();
178
+ builder.Property(x => x.Description).HasMaxLength(500);
179
+
180
+ // Audit (from IAuditableEntity)
181
+ builder.Property(x => x.CreatedBy).HasMaxLength(256);
182
+ builder.Property(x => x.UpdatedBy).HasMaxLength(256);
183
+
184
+ // Unique indexes
185
+ builder.HasIndex(x => new { x.TenantId, x.Code })
186
+ .IsUnique()
187
+ .HasDatabaseName("IX_{prefix}{Name}s_Tenant_Code");
188
+
189
+ // Relationships
190
+ // builder.HasMany(x => x.Children)
191
+ // .WithOne(x => x.Parent)
192
+ // .HasForeignKey(x => x.ParentId)
193
+ // .OnDelete(DeleteBehavior.Restrict);
194
+
195
+ // Seed data (if applicable)
196
+ // builder.HasData({Name}SeedData.GetSeedData());
197
+ }
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Service Pattern (tenant-scoped, MANDATORY)
204
+
205
+ > **CRITICAL:** ALL services MUST inject `ICurrentUser` and filter by `TenantId`. Missing TenantId = OWASP A01 vulnerability.
206
+
207
+ ```csharp
208
+ using Microsoft.EntityFrameworkCore;
209
+ using Microsoft.Extensions.Logging;
210
+ using SmartStack.Application.Common.Interfaces.Identity;
211
+ using SmartStack.Application.Common.Interfaces.Persistence;
212
+
213
+ namespace {ProjectName}.Infrastructure.Services.{Context}.{App}.{Module};
214
+
215
+ public class {Name}Service : I{Name}Service
216
+ {
217
+ private readonly IExtensionsDbContext _db;
218
+ private readonly ICurrentUser _currentUser;
219
+ private readonly ILogger<{Name}Service> _logger;
220
+
221
+ public {Name}Service(
222
+ IExtensionsDbContext db,
223
+ ICurrentUser currentUser,
224
+ ILogger<{Name}Service> logger)
225
+ {
226
+ _db = db;
227
+ _currentUser = currentUser;
228
+ _logger = logger;
229
+ }
230
+
231
+ public async Task<PaginatedResult<{Name}ResponseDto>> GetAllAsync(
232
+ string? search = null,
233
+ int page = 1,
234
+ int pageSize = 20,
235
+ CancellationToken ct = default)
236
+ {
237
+ var query = _db.{Name}s
238
+ .Where(x => x.TenantId == _currentUser.TenantId) // MANDATORY tenant filter
239
+ .AsNoTracking();
240
+
241
+ // Search filter — enables EntityLookup on frontend
242
+ if (!string.IsNullOrWhiteSpace(search))
243
+ {
244
+ query = query.Where(x =>
245
+ x.Name.Contains(search) ||
246
+ x.Code.Contains(search));
247
+ }
248
+
249
+ var totalCount = await query.CountAsync(ct);
250
+ var items = await query
251
+ .OrderBy(x => x.Name)
252
+ .Skip((page - 1) * pageSize)
253
+ .Take(pageSize)
254
+ .Select(x => new {Name}ResponseDto(x.Id, x.Code, x.Name, x.CreatedAt))
255
+ .ToListAsync(ct);
256
+
257
+ return new PaginatedResult<{Name}ResponseDto>(items, totalCount, page, pageSize);
258
+ }
259
+
260
+ public async Task<{Name}ResponseDto?> GetByIdAsync(Guid id, CancellationToken ct)
261
+ {
262
+ return await _db.{Name}s
263
+ .Where(x => x.Id == id && x.TenantId == _currentUser.TenantId) // MANDATORY
264
+ .AsNoTracking()
265
+ .Select(x => new {Name}ResponseDto(x.Id, x.Code, x.Name, x.CreatedAt))
266
+ .FirstOrDefaultAsync(ct);
267
+ }
268
+
269
+ public async Task<{Name}ResponseDto> CreateAsync(Create{Name}Dto dto, CancellationToken ct)
270
+ {
271
+ var entity = {Name}.Create(
272
+ tenantId: _currentUser.TenantId, // MANDATORY — never Guid.Empty
273
+ code: dto.Code,
274
+ name: dto.Name);
275
+
276
+ entity.CreatedBy = _currentUser.UserId?.ToString();
277
+
278
+ _db.{Name}s.Add(entity);
279
+ await _db.SaveChangesAsync(ct);
280
+
281
+ _logger.LogInformation("Created {Entity} {Id} for tenant {TenantId}",
282
+ nameof({Name}), entity.Id, _currentUser.TenantId);
283
+
284
+ return new {Name}ResponseDto(entity.Id, entity.Code, entity.Name, entity.CreatedAt);
285
+ }
286
+
287
+ public async Task DeleteAsync(Guid id, CancellationToken ct)
288
+ {
289
+ var entity = await _db.{Name}s
290
+ .FirstOrDefaultAsync(x => x.Id == id && x.TenantId == _currentUser.TenantId, ct)
291
+ ?? throw new KeyNotFoundException($"{Name} {id} not found");
292
+
293
+ _db.{Name}s.Remove(entity);
294
+ await _db.SaveChangesAsync(ct);
295
+ }
296
+ }
297
+ ```
298
+
299
+ **Key interfaces:**
300
+ - `ICurrentUser` (from `SmartStack.Application.Common.Interfaces.Identity`): provides `TenantId`, `UserId`, `Email`
301
+ - `IExtensionsDbContext` (for client extensions) or `ICoreDbContext` (for platform)
302
+
303
+ **FORBIDDEN in services:**
304
+ - `tenantId: Guid.Empty` — always use `_currentUser.TenantId`
305
+ - Queries WITHOUT `.Where(x => x.TenantId == _currentUser.TenantId)` — data leak
306
+ - Missing `ILogger<T>` — undiagnosable in production
307
+
308
+ ---
309
+
310
+ ## Controller Pattern (NavRoute)
311
+
312
+ ```csharp
313
+ using Microsoft.AspNetCore.Authorization;
314
+ using Microsoft.AspNetCore.Mvc;
315
+ using SmartStack.Api.Routing;
316
+ using SmartStack.Api.Authorization;
317
+
318
+ namespace {ProjectName}.Api.Controllers.{Context}.{App};
319
+
320
+ [ApiController]
321
+ [NavRoute("{context}.{app}.{module}")]
322
+ [Authorize]
323
+ public class {Name}Controller : ControllerBase
324
+ {
325
+ private readonly I{Name}Service _service;
326
+ private readonly ILogger<{Name}Controller> _logger;
327
+
328
+ public {Name}Controller(I{Name}Service service, ILogger<{Name}Controller> logger)
329
+ {
330
+ _service = service;
331
+ _logger = logger;
332
+ }
333
+
334
+ [HttpGet]
335
+ [RequirePermission(Permissions.{Module}.Read)]
336
+ public async Task<ActionResult<PaginatedResult<{Name}ResponseDto>>> GetAll(
337
+ [FromQuery] string? search = null,
338
+ [FromQuery] int page = 1,
339
+ [FromQuery] int pageSize = 20,
340
+ CancellationToken ct = default)
341
+ => Ok(await _service.GetAllAsync(search, page, pageSize, ct));
342
+
343
+ [HttpGet("{id:guid}")]
344
+ [RequirePermission(Permissions.{Module}.Read)]
345
+ public async Task<ActionResult<{Name}ResponseDto>> GetById(Guid id, CancellationToken ct)
346
+ {
347
+ var result = await _service.GetByIdAsync(id, ct);
348
+ return result is null ? NotFound() : Ok(result);
349
+ }
350
+
351
+ [HttpPost]
352
+ [RequirePermission(Permissions.{Module}.Create)]
353
+ public async Task<ActionResult<{Name}ResponseDto>> Create([FromBody] Create{Name}Dto dto, CancellationToken ct)
354
+ {
355
+ var result = await _service.CreateAsync(dto, ct);
356
+ return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
357
+ }
358
+
359
+ [HttpDelete("{id:guid}")]
360
+ [RequirePermission(Permissions.{Module}.Delete)]
361
+ public async Task<ActionResult> Delete(Guid id, CancellationToken ct)
362
+ {
363
+ await _service.DeleteAsync(id, ct);
364
+ return NoContent();
365
+ }
366
+ }
367
+ ```
368
+
369
+ **CRITICAL:** Use `[RequirePermission(Permissions.{Module}.{Action})]` on EVERY endpoint — NEVER `[Authorize]` alone (no RBAC enforcement).
370
+
371
+ **Namespace:** `SmartStack.Api.Routing` (NOT `SmartStack.Api.Core.Routing`)
372
+
373
+ **NavRoute resolves at startup from DB:** `platform.administration.users` → `api/platform/administration/users`
374
+
375
+ ---
376
+
377
+ ## Navigation Seed Data Pattern (CRITICAL — routes must be full paths)
378
+
379
+ > **The navigation seed data defines menu routes stored in DB. These routes MUST be full paths starting with `/`.**
380
+ > Short routes (e.g., `humanresources`) cause 400 Bad Request on application-tracking.
381
+
382
+ ### Route Convention
383
+
384
+ | Level | Route Format | Example |
385
+ |-------|-------------|---------|
386
+ | Application | `/{context}/{app-kebab}` | `/business/human-resources` |
387
+ | Module | `/{context}/{app-kebab}/{module-kebab}` | `/business/human-resources/employees` |
388
+ | Section | `/{context}/{app-kebab}/{module-kebab}/{section-kebab}` | `/business/human-resources/employees/list` |
389
+ | Resource | `/{context}/{app-kebab}/{module-kebab}/{section-kebab}/{resource-kebab}` | `/business/human-resources/employees/list/export` |
390
+
391
+ **Rules:**
392
+ - Routes ALWAYS start with `/`
393
+ - Routes ALWAYS include the full hierarchy from context to current level
394
+ - Routes ALWAYS use kebab-case (NOT PascalCase, NOT camelCase)
395
+ - Code identifiers stay PascalCase in C# (`HumanResources`) but routes are kebab-case (`human-resources`)
396
+
397
+ ### ToKebabCase Helper (include in SeedConstants or SeedDataProvider)
398
+
399
+ ```csharp
400
+ private static string ToKebabCase(string value)
401
+ => System.Text.RegularExpressions.Regex
402
+ .Replace(value, "(?<!^)([A-Z])", "-$1")
403
+ .ToLowerInvariant();
404
+ ```
405
+
406
+ ### SeedConstants Pattern
407
+
408
+ ```csharp
409
+ public static class SeedConstants
410
+ {
411
+ // Deterministic GUIDs (SHA256-based, reproducible across environments)
412
+ public static readonly Guid ApplicationId = DeterministicGuid("nav:business.humanresources");
413
+ public static readonly Guid ModuleId = DeterministicGuid("nav:business.humanresources.employees");
414
+ public static readonly Guid SectionId = DeterministicGuid("nav:business.humanresources.employees.list");
415
+
416
+ private static Guid DeterministicGuid(string input)
417
+ {
418
+ var hash = System.Security.Cryptography.SHA256.HashData(
419
+ System.Text.Encoding.UTF8.GetBytes(input));
420
+ var bytes = new byte[16];
421
+ Array.Copy(hash, bytes, 16);
422
+ bytes[6] = (byte)((bytes[6] & 0x0F) | 0x50); // version 5
423
+ bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80); // variant
424
+ return new Guid(bytes);
425
+ }
426
+ }
427
+ ```
428
+
429
+ ### Navigation Seed Data Example
430
+
431
+ ```csharp
432
+ // Application: /business/human-resources
433
+ var app = NavigationApplication.Create(
434
+ businessCtx.Id, "humanresources", "Human Resources", "HR Management",
435
+ "Users", IconType.Lucide,
436
+ "/business/human-resources", // FULL PATH — starts with /, kebab-case
437
+ 10);
438
+
439
+ // Module: /business/human-resources/employees
440
+ var module = NavigationModule.Create(
441
+ app.Id, "employees", "Employees", "Employee management",
442
+ "UserCheck", IconType.Lucide,
443
+ "/business/human-resources/employees", // FULL PATH — includes parent
444
+ 10);
445
+
446
+ // Section: /business/human-resources/employees/departments
447
+ var section = NavigationSection.Create(
448
+ module.Id, "departments", "Departments", "Manage departments",
449
+ "Building2", IconType.Lucide,
450
+ "/business/human-resources/employees/departments", // FULL PATH
451
+ 10);
452
+ ```
453
+
454
+ ### FORBIDDEN in Seed Data
455
+
456
+ | Mistake | Reality |
457
+ |---------|---------|
458
+ | `"humanresources"` as route | Must be `"/business/human-resources"` (full path, kebab-case) |
459
+ | `"employees"` as route | Must be `"/business/human-resources/employees"` (includes parent) |
460
+ | `Guid.NewGuid()` in seed data | Must use deterministic GUIDs (SHA256) |
461
+ | Missing translations | Must have 4 languages: fr, en, it, de |
462
+ | Missing NavigationApplicationSeedData | Menu invisible without Application level |
463
+
464
+ ---
465
+
466
+ ## DbContext Pattern (extensions)
467
+
468
+ ```csharp
469
+ // In IExtensionsDbContext.cs:
470
+ public DbSet<{Name}> {Name}s => Set<{Name}>();
471
+
472
+ // In ExtensionsDbContext.cs (same line):
473
+ public DbSet<{Name}> {Name}s => Set<{Name}>();
474
+ ```
475
+
476
+ ---
477
+
478
+ ## DI Registration Pattern
479
+
480
+ ```csharp
481
+ // In DependencyInjection.cs or ServiceCollectionExtensions.cs:
482
+ services.AddScoped<I{Name}Service, {Name}Service>();
483
+ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
484
+ ```
485
+
486
+ ---
487
+
488
+ ## Common Mistakes to Avoid
489
+
490
+ | Mistake | Reality |
491
+ |---------|---------|
492
+ | `entity.SoftDelete()` | Does NOT exist — no soft delete in BaseEntity |
493
+ | `entity.Code` inherited | Code is a business property — add it yourself |
494
+ | `e.RowVersion` in config | Does NOT exist in BaseEntity |
495
+ | `e.IsDeleted` filter | Does NOT exist — no soft delete |
496
+ | `SmartStack.Api.Core.Routing` | Wrong — use `SmartStack.Api.Routing` |
497
+ | `SystemEntity` base class | Does NOT exist — use `BaseEntity` for all |
498
+ | `[Route] + [NavRoute]` | Only `[NavRoute]` needed (resolves route from DB) |
499
+ | `SmartStack.Domain.Common.Interfaces` | Wrong — interfaces are in `SmartStack.Domain.Common` directly |
500
+ | `[Authorize]` without `[RequirePermission]` | No RBAC enforcement — always use `[RequirePermission]` |
501
+ | `tenantId: Guid.Empty` in services | OWASP A01 — always use `_currentUser.TenantId` |
502
+ | Service without `ICurrentUser` | All tenant data leaks — inject `ICurrentUser` |
503
+ | Route `"humanresources"` in seed data | Must be full path `"/business/human-resources"` |
504
+ | Route without leading `/` | All routes must start with `/` |
505
+ | `Permission.Create()` | Does NOT exist — use `CreateForModule()`, `CreateForSection()`, etc. |
506
+ | `GetAllAsync()` without search param | ALL GetAll endpoints MUST support `?search=` for EntityLookup |
507
+ | FK field as plain text input | Frontend MUST use `EntityLookup` component for Guid FK fields |