@polymorphism-tech/morph-spec 4.8.6 → 4.8.8

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 (33) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +22 -1
  3. package/bin/task-manager.cjs +120 -16
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/agents.json +1854 -1815
  8. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +141 -23
  9. package/framework/hooks/claude-code/statusline.py +304 -280
  10. package/framework/hooks/claude-code/statusline.sh +6 -2
  11. package/framework/hooks/claude-code/stop/validate-completion.js +70 -23
  12. package/framework/hooks/dev/guard-version-numbers.js +1 -1
  13. package/framework/skills/level-0-meta/morph-init/SKILL.md +44 -6
  14. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +67 -16
  15. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +1 -1
  16. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +77 -7
  17. package/framework/skills/level-1-workflows/phase-design/SKILL.md +114 -50
  18. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +139 -1
  19. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +29 -6
  20. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +4 -3
  21. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +1 -1
  22. package/framework/standards/STANDARDS.json +944 -933
  23. package/framework/standards/architecture/vertical-slice/vertical-slice.md +429 -0
  24. package/framework/templates/REGISTRY.json +1909 -1888
  25. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +282 -0
  26. package/package.json +1 -1
  27. package/src/commands/agents/dispatch-agents.js +430 -0
  28. package/src/commands/agents/index.js +2 -1
  29. package/src/commands/project/doctor.js +137 -2
  30. package/src/commands/state/state.js +20 -4
  31. package/src/commands/templates/generate-contracts.js +445 -0
  32. package/src/commands/templates/index.js +1 -0
  33. package/src/lib/validators/validation-runner.js +19 -7
@@ -0,0 +1,429 @@
1
+ # Vertical Slice Architecture (VSA)
2
+
3
+ > **Scope:** universal
4
+ > **Layer:** 2
5
+ > **Keywords:** vertical-slice, vsa, minimal-api, ihandler, result-pattern, feature-slice, scrutor, fluentvalidation
6
+
7
+ Padrão arquitetural baseado em **KanaiyaKatarmal/VerticalSliceArchitectureTemplate** (.NET 10, Minimal APIs, EF Core 10).
8
+ Cada feature é uma fatia vertical completa: endpoint → handler → validator → entity → DB.
9
+ Sem MediatR, sem AggregateRoot, sem Domain Events, sem CQRS formal.
10
+
11
+ ---
12
+
13
+ ## Estrutura de Pastas
14
+
15
+ ```
16
+ {ProjectName}/
17
+ ├── Abstractions/
18
+ │ ├── IHandler.cs # Interface central: IHandler<TRequest, TResponse>
19
+ │ ├── IApiEndpoint.cs # Interface de endpoint: void MapEndpoint(WebApplication app)
20
+ │ ├── Result.cs # Result<T> / Result (non-generic)
21
+ │ └── Errors/
22
+ │ ├── Error.cs # record Error com factory methods + ErrorType enum
23
+ │ └── ValidationError.cs # ValidationError : Error com array de erros
24
+ ├── Constants/
25
+ │ └── ApiTags.cs # Constantes de tags para OpenAPI/Swagger
26
+ ├── Database/
27
+ │ └── ApplicationDbContext.cs # DbContext com DbSet<Entity>
28
+ ├── Entities/
29
+ │ └── {Entity}.cs # Entidade simples (sem AggregateRoot)
30
+ ├── Exceptions/
31
+ │ └── CustomExceptionHandler.cs # IExceptionHandler para ProblemDetails
32
+ ├── Extensions/
33
+ │ ├── HandlerRegistrationExtensions.cs # AddHandlersFromAssembly() com Scrutor Decorate
34
+ │ ├── MapEndpointExtensions.cs # RegisterApiEndpointsFromAssembly() + MapApiEndpoints()
35
+ │ └── ResultExtensions.cs # result.Match(onSuccess, onFailure)
36
+ ├── Features/
37
+ │ └── {Entity}Feature/
38
+ │ ├── {Entity}Errors.cs # static class {Entity}Errors (factory methods)
39
+ │ ├── Create{Entity}/
40
+ │ │ ├── Create{Entity}Request.cs # sealed record request
41
+ │ │ ├── ...Response.cs # sealed record response
42
+ │ │ ├── Create{Entity}Handler.cs # IHandler<Request, Result<Response>>
43
+ │ │ ├── Create{Entity}Validator.cs # AbstractValidator<Request>
44
+ │ │ └── Create{Entity}Endpoint.cs # IApiEndpoint implementação
45
+ │ ├── GetAll{Entity}s/ # Handler (sem validator)
46
+ │ ├── Get{Entity}ById/ # Handler + Validator
47
+ │ ├── Update{Entity}/ # Handler + Validator
48
+ │ └── Delete{Entity}/ # Handler + Validator
49
+ ├── Migrations/ # EF Core migrations
50
+ ├── Pipelines/
51
+ │ ├── ValidationDecorator.cs # Decorator que executa validators
52
+ │ └── LoggingDecorator.cs # Decorator que loga request/failure
53
+ ├── Repository/
54
+ │ ├── IRepository.cs # IRepository<T> genérico
55
+ │ ├── Repository.cs # Repository<T> : IRepository<T>
56
+ │ ├── IUnitOfWork.cs # IUnitOfWork
57
+ │ └── UnitOfWork.cs # UnitOfWork
58
+ └── Program.cs
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Abstractions (código real)
64
+
65
+ ### `IHandler<TRequest, TResponse>`
66
+
67
+ ```csharp
68
+ namespace {ProjectName}.Abstractions
69
+ {
70
+ public interface IHandler<in TRequest, TResponse>
71
+ {
72
+ Task<TResponse> HandleAsync(TRequest command, CancellationToken cancellationToken);
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### `IApiEndpoint`
78
+
79
+ ```csharp
80
+ namespace {ProjectName}.Abstractions
81
+ {
82
+ public interface IApiEndpoint
83
+ {
84
+ void MapEndpoint(WebApplication app);
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### `Error` (record com factory methods)
90
+
91
+ ```csharp
92
+ namespace {ProjectName}.Abstractions.Errors;
93
+
94
+ public record Error(string Code, string? Description = default, ErrorType Type = ErrorType.Failure)
95
+ {
96
+ public static readonly Error None = new(string.Empty);
97
+ public static readonly Error Null = new("Error.NullValue", "The specified result value is null.");
98
+
99
+ public static implicit operator Result(Error error) => Result.Failure(error);
100
+
101
+ public static Error Failure(string code, string description) =>
102
+ new(code, description, ErrorType.Failure);
103
+ public static Error Unexpected(string code, string description) =>
104
+ new(code, description, ErrorType.Unexpected);
105
+ public static Error Validation(string code, string description) =>
106
+ new(code, description, ErrorType.Validation);
107
+ public static Error Conflict(string code, string description) =>
108
+ new(code, description, ErrorType.Conflict);
109
+ public static Error NotFound(string code, string description) =>
110
+ new(code, description, ErrorType.NotFound);
111
+ public static Error Unauthorized(string code, string description) =>
112
+ new(code, description, ErrorType.Unauthorized);
113
+ public static Error Forbidden(string code, string description) =>
114
+ new(code, description, ErrorType.Forbidden);
115
+ }
116
+
117
+ public enum ErrorType
118
+ {
119
+ Failure, Unexpected, Validation, Conflict, NotFound, Unauthorized, Forbidden, Custom
120
+ }
121
+ ```
122
+
123
+ ### `ValidationError`
124
+
125
+ ```csharp
126
+ namespace {ProjectName}.Abstractions.Errors;
127
+
128
+ public sealed record ValidationError : Error
129
+ {
130
+ public ValidationError(Error[] errors)
131
+ : base("Validation.General", "One or more validation errors occurred", ErrorType.Validation)
132
+ {
133
+ Errors = errors;
134
+ }
135
+ public Error[] Errors { get; }
136
+ public static ValidationError FromResults(IEnumerable<Result> results) =>
137
+ new(results.Where(r => r.IsFailure).Select(r => r.Error).ToArray());
138
+ }
139
+ ```
140
+
141
+ ### `Result<T>` / `Result`
142
+
143
+ ```csharp
144
+ namespace {ProjectName}.Abstractions;
145
+
146
+ public class Result
147
+ {
148
+ protected internal Result(bool isSuccess, Error error) { ... }
149
+ public bool IsSuccess { get; }
150
+ public bool IsFailure => !IsSuccess;
151
+ public Error Error { get; }
152
+
153
+ public static Result Success() => new(true, Error.None);
154
+ public static Result<TValue> Success<TValue>(TValue value) => new(value, true, Error.None);
155
+ public static Result Failure(Error error) => new(false, error);
156
+ public static Result<TValue> Failure<TValue>(Error error) => new(default, false, error);
157
+ public static Result<TValue> Create<TValue>(TValue? value) =>
158
+ value is not null ? Success(value) : Failure<TValue>(Error.Null);
159
+ }
160
+
161
+ public class Result<TValue> : Result
162
+ {
163
+ private readonly TValue? _value;
164
+ public TValue Value => IsSuccess ? _value! : throw new InvalidOperationException("...");
165
+
166
+ // Implicit conversions
167
+ public static implicit operator Result<TValue>(TValue? value) => Create(value);
168
+ public static implicit operator Result<TValue>(Error error) => Failure<TValue>(error);
169
+ }
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Anatomia de uma Slice (5 arquivos por operação)
175
+
176
+ Cada operação CRUD vive num subfolder dentro de `Features/{Entity}Feature/`:
177
+
178
+ ### 1. Request + Response + Handler (1 arquivo)
179
+
180
+ ```csharp
181
+ // Features/BookFeature/CreateBook/CreateBookHandler.cs
182
+ namespace {ProjectName}.Features.BookFeature.CreateBook;
183
+
184
+ // Records definidos no mesmo arquivo do Handler
185
+ public sealed record CreateBookRequest(string Title, string Author, string ISBN, decimal Price, int PublishedYear);
186
+ public sealed record CreateBookResponse(Guid Id, string Title, string Author, string ISBN, decimal Price, int PublishedYear);
187
+
188
+ public sealed class CreateBookHandler(
189
+ IRepository<Book> _bookRepo,
190
+ IUnitOfWork _unitOfWork) : IHandler<CreateBookRequest, Result<CreateBookResponse>>
191
+ {
192
+ public async Task<Result<CreateBookResponse>> HandleAsync(
193
+ CreateBookRequest command, CancellationToken cancellationToken)
194
+ {
195
+ var book = new Book
196
+ {
197
+ Id = Guid.CreateVersion7(),
198
+ Title = command.Title,
199
+ Author = command.Author,
200
+ ISBN = command.ISBN,
201
+ Price = command.Price,
202
+ PublishedYear = command.PublishedYear
203
+ };
204
+ await _bookRepo.AddAsync(book, cancellationToken);
205
+ await _unitOfWork.CommitAsync(cancellationToken);
206
+ return Result.Success(new CreateBookResponse(
207
+ book.Id, book.Title, book.Author, book.ISBN, book.Price, book.PublishedYear));
208
+ }
209
+ }
210
+ ```
211
+
212
+ ### 2. Validator
213
+
214
+ ```csharp
215
+ // Features/BookFeature/CreateBook/CreateBookValidator.cs
216
+ using FluentValidation;
217
+ namespace {ProjectName}.Features.BookFeature.CreateBook;
218
+
219
+ public class CreateBookValidator : AbstractValidator<CreateBookRequest>
220
+ {
221
+ public CreateBookValidator()
222
+ {
223
+ RuleFor(c => c.Title).NotEmpty().MaximumLength(200);
224
+ RuleFor(c => c.Author).NotEmpty().MaximumLength(100);
225
+ RuleFor(c => c.ISBN).NotEmpty();
226
+ RuleFor(c => c.Price).GreaterThan(0);
227
+ RuleFor(c => c.PublishedYear)
228
+ .GreaterThan(1000)
229
+ .LessThanOrEqualTo(DateTime.UtcNow.Year);
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### 3. Endpoint (Minimal API)
235
+
236
+ ```csharp
237
+ // Features/BookFeature/CreateBook/CreateBookEndpoint.cs
238
+ namespace {ProjectName}.Features.BookFeature.CreateBook;
239
+
240
+ internal sealed class CreateBookEndpoint : IApiEndpoint
241
+ {
242
+ public void MapEndpoint(WebApplication app)
243
+ {
244
+ app.MapPost("books", async (
245
+ IHandler<CreateBookRequest, Result<CreateBookResponse>> handler,
246
+ CreateBookRequest command,
247
+ CancellationToken cancellationToken) =>
248
+ {
249
+ var result = await handler.HandleAsync(command, cancellationToken);
250
+ return result.Match(
251
+ onSuccess: () => Results.Ok(result.Value),
252
+ onFailure: error => Results.BadRequest(error));
253
+ })
254
+ .WithTags(ApiTags.Books)
255
+ .Produces<CreateBookResponse>(StatusCodes.Status200OK)
256
+ .Produces(StatusCodes.Status400BadRequest);
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### 4. Errors (por feature, não por operação)
262
+
263
+ ```csharp
264
+ // Features/BookFeature/BookErrors.cs
265
+ using {ProjectName}.Abstractions.Errors;
266
+ namespace {ProjectName}.Features.BookFeature;
267
+
268
+ public static class BookErrors
269
+ {
270
+ public static Error NotFound(Guid id) =>
271
+ Error.NotFound("Books.NotFound", $"The Book with Id '{id}' was not found");
272
+ }
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Pipelines (Decorators)
278
+
279
+ Os handlers são decorados automaticamente via Scrutor:
280
+
281
+ ### ValidationDecorator
282
+
283
+ - Executa todos os `IValidator<TRequest>` registrados
284
+ - Se houver falhas → retorna `Result.Failure(new ValidationError([...]))` sem chamar o handler
285
+ - Se `TResponse` não for `Result` ou `Result<T>` → lança `InvalidOperationException`
286
+
287
+ ### LoggingDecorator
288
+
289
+ - Loga `Handling request {RequestName}` antes
290
+ - Se `result.IsFailure` → `LogWarning` com `result.Error.Code`
291
+ - Caso contrário → `LogInformation` "Handled request"
292
+
293
+ ### Ordem de registro (via `AddHandlersFromAssembly`)
294
+
295
+ ```csharp
296
+ // Extensions/HandlerRegistrationExtensions.cs
297
+ services.Decorate(typeof(IHandler<,>), typeof(ValidationDecorator<,>));
298
+ services.Decorate(typeof(IHandler<,>), typeof(LoggingDecorator<,>));
299
+ ```
300
+
301
+ Ordem de execução (innermost-first): `LoggingDecorator → ValidationDecorator → Handler`
302
+
303
+ ---
304
+
305
+ ## Repository Pattern
306
+
307
+ ```csharp
308
+ // Repository/IRepository.cs
309
+ public interface IRepository<T> where T : class
310
+ {
311
+ Task<IEnumerable<T>> GetAllAsync(CancellationToken cancellationToken = default);
312
+ Task<T?> GetByIdAsync(object id, CancellationToken cancellationToken = default);
313
+ Task<T> AddAsync(T entity, CancellationToken cancellationToken = default);
314
+ Task UpdateAsync(T entity, CancellationToken cancellationToken = default);
315
+ Task DeleteAsync(T entity, CancellationToken cancellationToken = default);
316
+ IEnumerable<T> ExecuteQuery(string query, DbParameter[] dbParams);
317
+ Task AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);
318
+ void RemoveRange(IEnumerable<T> entities);
319
+ }
320
+ ```
321
+
322
+ ---
323
+
324
+ ## DI Registration (Program.cs)
325
+
326
+ ```csharp
327
+ // Repositories + Unit of Work
328
+ builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
329
+ builder.Services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork));
330
+
331
+ // EF Core (SQLite/SqlServer/PostgreSQL)
332
+ builder.Services.AddSQLDatabaseConfiguration(builder.Configuration);
333
+
334
+ // Endpoints (auto-discovery via reflection)
335
+ builder.Services.RegisterApiEndpointsFromAssembly(Assembly.GetExecutingAssembly());
336
+
337
+ // FluentValidation (auto-discovery)
338
+ builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);
339
+
340
+ // Handlers + Scrutor decorators (ValidationDecorator + LoggingDecorator)
341
+ builder.Services.AddHandlersFromAssembly(typeof(Program).Assembly);
342
+
343
+ // Exception handler + ProblemDetails
344
+ builder.Services.AddExceptionHandler<CustomExceptionHandler>().AddProblemDetails();
345
+
346
+ // Map endpoints
347
+ app.MapApiEndpoints();
348
+ ```
349
+
350
+ ---
351
+
352
+ ## Convenções de Nomenclatura
353
+
354
+ | Elemento | Convenção | Exemplo |
355
+ |----------|-----------|---------|
356
+ | Feature folder | `{Entity}Feature` | `BookFeature` |
357
+ | Operation folder | `{Verb}{Entity}` | `CreateBook`, `GetBookById` |
358
+ | Request record | `{Verb}{Entity}Request` | `CreateBookRequest` |
359
+ | Response record | `{Verb}{Entity}Response` | `CreateBookResponse` |
360
+ | Handler class | `{Verb}{Entity}Handler` | `CreateBookHandler` |
361
+ | Validator class | `{Verb}{Entity}Validator` | `CreateBookValidator` |
362
+ | Endpoint class | `{Verb}{Entity}Endpoint` | `CreateBookEndpoint` |
363
+ | Errors class | `{Entity}Errors` | `BookErrors` (static, shared por feature) |
364
+ | DTO record (GetAll) | `{Entity}Dto` | `BookDto` |
365
+ | API tag constant | `ApiTags.{Entities}` | `ApiTags.Books` |
366
+ | Entity class | `{Entity}` (`sealed`) | `Book` |
367
+
368
+ ---
369
+
370
+ ## Padrões Obrigatórios
371
+
372
+ 1. **Handler records no mesmo arquivo**: `Request` e `Response` são declarados no mesmo arquivo `.cs` que o `Handler`.
373
+ 2. **`sealed` em tudo**: handlers, endpoints, validators, request/response records usam `sealed`.
374
+ 3. **`Guid.CreateVersion7()`** para gerar IDs (não `Guid.NewGuid()`).
375
+ 4. **`result.Match()`** nos endpoints — nunca acessar `result.Value` sem checar `IsSuccess`.
376
+ 5. **IApiEndpoint `internal sealed`** — endpoints não são públicos.
377
+ 6. **`{Entity}Errors` compartilhado** por todas as operações da feature (1 arquivo por feature, não por slice).
378
+ 7. **Repository injetado pelo handler** diretamente (sem Application Service layer).
379
+ 8. **`IUnitOfWork.CommitAsync()`** em operações de escrita (Create, Update, Delete).
380
+ 9. **Sem validator em GetAll** — GetAllBooksHandler não tem validator (sem parâmetros para validar).
381
+
382
+ ---
383
+
384
+ ## Anti-patterns (NUNCA fazer em VSA)
385
+
386
+ | Anti-pattern | Por quê não |
387
+ |--------------|-------------|
388
+ | `AggregateRoot` | VSA não usa DDD — entidade simples com `sealed class` |
389
+ | `DomainEvent` | VSA não tem Domain Events — reações são handlers separados |
390
+ | `MediatR` / `IRequest` / `IRequestHandler` | Substituído por `IHandler<TRequest, TResponse>` próprio |
391
+ | DTOs compartilhados entre features | Cada slice define seus próprios records |
392
+ | Application Service layer | Handler acessa Repository diretamente |
393
+ | CQRS formal (Commands/Queries) | Handler simples, sem distinção Command/Query |
394
+ | Shared Validators em Application layer | Validator fica no mesmo namespace do slice |
395
+
396
+ ---
397
+
398
+ ## NuGet Packages (versões exatas do template)
399
+
400
+ ```xml
401
+ <PackageReference Include="FluentValidation" Version="12.1.1" />
402
+ <PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
403
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
404
+ <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
405
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.3" />
406
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
407
+ <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
408
+ <PackageReference Include="Scalar.AspNetCore" Version="2.12.40" />
409
+ <PackageReference Include="Scrutor" Version="7.0.0" />
410
+ <PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
411
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.3" />
412
+ ```
413
+
414
+ **Target Framework:** `net10.0`
415
+
416
+ ---
417
+
418
+ ## Quando Usar VSA vs DDD
419
+
420
+ | Critério | VSA | DDD (Levels 1-3) |
421
+ |----------|-----|------------------|
422
+ | Feature é CRUD com validações | ✅ Ideal | Overkill |
423
+ | Feature tem regras de negócio complexas | ⚠️ Possível mas complexo | ✅ Ideal |
424
+ | Feature tem Domain Events com consumidores | ❌ Fora do escopo | ✅ Ideal |
425
+ | Time prefere simplicidade / explicitness | ✅ | ⚠️ |
426
+ | Projeto com múltiplos Bounded Contexts | ❌ | ✅ Ideal |
427
+ | Stack .NET 10 + Minimal APIs | ✅ Native | ✅ Adaptável |
428
+
429
+ **Regra prática:** Se o projeto já usa VSA (detectado por `Abstractions/IHandler.cs`), continue com VSA. Não misture VSA e DDD no mesmo projeto.