@polymorphism-tech/morph-spec 9.3.0 → 9.4.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 (47) hide show
  1. package/README.md +2 -2
  2. package/claude-plugin.json +1 -1
  3. package/docs/CHEATSHEET.md +1 -1
  4. package/docs/QUICKSTART.md +1 -1
  5. package/framework/agents.json +6 -4
  6. package/framework/docs/CLI.md +1 -1
  7. package/framework/evals/review-checklist.md +1 -1
  8. package/framework/hooks/README.md +1 -0
  9. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -15
  10. package/framework/hooks/claude-code/core/conventional-commit.js +57 -0
  11. package/framework/hooks/shared/command-tokens.js +17 -0
  12. package/framework/rules/csharp-standards.md +5 -3
  13. package/framework/rules/dotnet-templates.md +2 -2
  14. package/framework/rules/nextjs-standards.md +1 -0
  15. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  16. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  17. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  18. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  19. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  20. package/framework/skills/core/morph-media/SKILL.md +1 -1
  21. package/framework/skills/core/morph-plan/SKILL.md +3 -3
  22. package/framework/skills/core/morph-review/SKILL.md +1 -1
  23. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  24. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  25. package/framework/standards/STANDARDS.json +55 -8
  26. package/framework/standards/STANDARDS.meta.json +28 -0
  27. package/framework/standards/architecture/engineering-lens.md +100 -0
  28. package/framework/standards/architecture/vertical-slice/vertical-slice.md +7 -9
  29. package/framework/standards/backend/dotnet/analyzers.md +59 -0
  30. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -12
  31. package/framework/templates/README.md +0 -1
  32. package/framework/templates/REGISTRY.json +0 -207
  33. package/package.json +1 -1
  34. package/src/commands/project/worktree.js +1 -1
  35. package/src/commands/state/archive.js +1 -1
  36. package/src/commands/state/finish.js +1 -1
  37. package/src/core/templates/template-registry.js +1 -43
  38. package/src/lib/stack/stack-scan.js +4 -6
  39. package/src/utils/agents-installer.js +1 -1
  40. package/src/utils/hooks-installer.js +1 -1
  41. package/framework/templates/meta-prompts/fusion/fusion-agent.md +0 -76
  42. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +0 -100
  43. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +0 -90
  44. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +0 -84
  45. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +0 -44
  46. package/framework/templates/meta-prompts/validators/checkpoint-validator.md +0 -107
  47. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +0 -94
@@ -0,0 +1,100 @@
1
+ # Lente de engenharia — código para quem herda amanhã
2
+
3
+ > **Scope:** universal
4
+ > **Layer:** 0 (resumo injetado pelas rules `csharp-standards` (`**/*.cs`, `**/*.csproj`) e `nextjs-standards` (`**/*.ts`, `**/*.tsx`); texto completo sob demanda via `morph-spec standards --find`)
5
+ > **Keywords:** clean code, código limpo, fragilidade, código morto, excesso, legibilidade, extensibilidade, flag booleana, efeito colateral, conventional commits
6
+ > **Load When:** escrevendo, planejando ou revisando código em qualquer stack
7
+
8
+ **Verified against:** C#, TypeScript e JavaScript (convenção de linguagem, sem pacote externo); Conventional Commits 1.0.0. Last-verified: 2026-10-18.
9
+
10
+ ---
11
+
12
+ ## Quem herda amanhã
13
+
14
+ Escreva para quem abre o repo amanhã e precisa entregar uma feature na semana seguinte. A
15
+ convenção que o projeto já usa vence o seu gosto. O mais simples que resolve vence o mais geral.
16
+ Bug se corrige na causa raiz, nunca no sintoma.
17
+
18
+ ## Fragilidade
19
+
20
+ Código que quebra fácil:
21
+
22
+ - `catch` vazio, ou que só loga e segue como se nada tivesse acontecido.
23
+ - Null ou ausência vindo de fronteira (HTTP, banco, arquivo, LLM) sem tratamento.
24
+ - Dependência de ordem implícita: método que só funciona se outro rodou antes.
25
+ - Parser estrito diante de dado real (campo opcional tratado como obrigatório, enum fechado para
26
+ valor externo).
27
+ - Guard cego: a checagem e o dado saem do mesmo serializador, então ela nunca falha.
28
+ - Teste que passa sem provar a regra (ver `backend-dotnet-testing` / `frontend-nextjs-testing`).
29
+
30
+ ## Código morto
31
+
32
+ Símbolo, endpoint, flag, config ou tabela sem consumidor real sai. Feature começada e largada
33
+ sai. Dois caminhos que fazem a mesma coisa viram um. Quando "X só é usado por Y", pergunte para
34
+ quê Y existe: muitas vezes os dois saem. Código comentado sai, porque o git guarda o antigo.
35
+
36
+ ## Excesso
37
+
38
+ - Abstração (interface, factory, base class, strategy) sem um segundo uso hoje.
39
+ - Camada que só repassa a chamada.
40
+ - Config ou parâmetro para um caso que ninguém pediu (o "e se um dia eu quiser voar com o carro":
41
+ construa as asas quando for voar).
42
+ - Stdlib, plataforma ou dependência já instalada reinventada à mão.
43
+
44
+ No plano, todo CRIAR de abstração diz no `why` o segundo uso ou a próxima adição que ela barateia.
45
+
46
+ ## Legibilidade
47
+
48
+ - **Nomes:** descritivos, pronunciáveis, buscáveis, na linguagem do domínio. Distinção com
49
+ significado (`sourceAccount`/`targetAccount`, nunca `account1`/`account2`). Sem prefixo de tipo
50
+ nem caractere especial. Classe é substantivo, método é verbo, e um padrão aparece no nome
51
+ (`InvoiceFactory`). Um idioma por identificador.
52
+ - **Funções:** pequenas, com um objetivo só. Poucos parâmetros (passou de 3, agrupe num tipo).
53
+ Sem efeito colateral fora do próprio objeto que o nome não anuncie.
54
+ - **Sem flag booleana que bifurca o método** — são dois métodos:
55
+
56
+ ```ts
57
+ // ruim
58
+ sendInvoice(invoice, true);
59
+ // bom
60
+ sendInvoiceByEmail(invoice);
61
+ sendInvoiceByWhatsApp(invoice);
62
+ ```
63
+
64
+ - **Condicional positiva:** `if (order.isEmpty)` em vez de `if (!order.hasItems)`; o `!` some na leitura.
65
+ - **Literal repetido vira constante** (detalhe em `architecture-code-comments`).
66
+ - **Estrutura:** por feature/slice (`architecture-vertical-slice-vertical-slice`); variável declarada
67
+ perto do uso; funções de cima para baixo, a chamadora antes da chamada; funções afins juntas; sem
68
+ alinhamento horizontal; formatação pelo formatter do projeto.
69
+ - **Comentários:** `architecture-code-comments` é a regra única.
70
+
71
+ ## Extensibilidade
72
+
73
+ A próxima adição provável (uma skill nova, um campo novo de config refletido na tela, um provider
74
+ novo) deve tocar poucos arquivos e nenhum `switch` espalhado. Se exige editar N lugares que precisam
75
+ concordar, o atrito é o registro que falta, não a falta de uma camada genérica.
76
+
77
+ ## IA e código
78
+
79
+ Regra que nunca pode ser quebrada fica em código, nunca vira prompt (`ai-agents-guardrails`). O
80
+ inverso também vale: regex, heurística ou árvore de `if` que classifica linguagem natural é candidata
81
+ a structured output, e a troca só entra medida por eval (`ai-agents-evals-with-cache`).
82
+
83
+ ## Git
84
+
85
+ A branch é `morph/{f}`, criada pelo CLI. Commits seguem Conventional Commits:
86
+ `<tipo>(<escopo>): <assunto>`, com o tipo em `feat`, `fix`, `refactor`, `test`, `chore`, `docs`,
87
+ `perf`, `ci`, `build`, `style` ou `revert`. O hook `conventional-commit` recusa outra forma.
88
+
89
+ ## O que bloqueia no Gate 3
90
+
91
+ Só o que o **diff introduziu**. O que já existia no arquivo vira nota.
92
+
93
+ 1. `catch` que engole a exceção.
94
+ 2. Flag booleana que bifurca o método.
95
+ 3. Efeito colateral escondido: o método muda estado alheio e o nome não diz.
96
+ 4. Símbolo, flag ou config sem consumidor, ou código comentado.
97
+ 5. Abstração ou camada sem segundo uso e sem `why` no plano.
98
+ 6. Literal mágico repetido em comparação.
99
+
100
+ O resto (nome, tamanho, ordem, "eu faria diferente") é nota.
@@ -380,20 +380,18 @@ app.MapApiEndpoints();
380
380
  | Errors class | `{Entity}Errors` | `BookErrors` (static, shared por feature) |
381
381
  | DTO record (GetAll) | `{Entity}Dto` | `BookDto` |
382
382
  | API tag constant | `ApiTags.{Entities}` | `ApiTags.Books` |
383
- | Entity class | `{Entity}` (`sealed`) | `Book` |
383
+ | Entity class | `{Entity}` | `Book` |
384
384
 
385
385
  ---
386
386
 
387
387
  ## Padrões Obrigatórios
388
388
 
389
389
  1. **Handler records no mesmo arquivo**: `Request` e `Response` são declarados no mesmo arquivo `.cs` que o `Handler`.
390
- 2. **`sealed` em tudo**: handlers, endpoints, validators, request/response records usam `sealed`.
391
- 3. **`Guid.CreateVersion7()`** para gerar IDs (não `Guid.NewGuid()`).
392
- 4. **`result.Match()`** nos endpoints — o sucesso recebe o valor no lambda (nunca ler `result.Value`) e a falha vira `error.ToHttpResult()` (status pelo `ErrorType`, nunca `Results.BadRequest` para todo erro).
393
- 5. **IApiEndpoint `internal sealed`** — endpoints não são públicos.
394
- 6. **`{Entity}Errors` compartilhado** por todas as operações da feature (1 arquivo por feature, não por slice).
395
- 7. **`ApplicationDbContext` direto no slice**: CRUD por chave via `DbSet<T>`; queries específicas (joins, projeção, paginação) via LINQ no handler. Nunca uma Application Service layer entre handler e dados; nunca repository genérico (`IRepository<T>`/`IUnitOfWork`).
396
- 8. **`SaveChangesAsync()`** explícito em operações de escrita (Create, Update, Delete).
390
+ 2. **`result.Match()`** nos endpoints — o sucesso recebe o valor no lambda (nunca ler `result.Value`) e a falha vira `error.ToHttpResult()` (status pelo `ErrorType`, nunca `Results.BadRequest` para todo erro).
391
+ 3. **IApiEndpoint `internal`** — endpoints não são públicos.
392
+ 4. **`{Entity}Errors` compartilhado** por todas as operações da feature (1 arquivo por feature, não por slice).
393
+ 5. **`ApplicationDbContext` direto no slice**: CRUD por chave via `DbSet<T>`; queries específicas (joins, projeção, paginação) via LINQ no handler. Nunca uma Application Service layer entre handler e dados; nunca repository genérico (`IRepository<T>`/`IUnitOfWork`).
394
+ 6. **`SaveChangesAsync()`** explícito em operações de escrita (Create, Update, Delete).
397
395
  9. **Sem validator em GetAll** — GetAllBooksHandler não tem validator (sem parâmetros para validar).
398
396
 
399
397
  ---
@@ -402,7 +400,7 @@ app.MapApiEndpoints();
402
400
 
403
401
  | Anti-pattern | Por quê não |
404
402
  |--------------|-------------|
405
- | `AggregateRoot` | VSA não usa DDD — entidade simples com `sealed class` |
403
+ | `AggregateRoot` | VSA não usa DDD — entidade simples |
406
404
  | `DomainEvent` | VSA não tem Domain Events — reações são handlers separados |
407
405
  | `MediatR` / `IRequest` / `IRequestHandler` | Substituído por `IHandler<TRequest, TResponse>` próprio |
408
406
  | DTOs compartilhados entre features | Cada slice define seus próprios records |
@@ -0,0 +1,59 @@
1
+ # Analyzers .NET — o build cobra, a prosa não repete
2
+
3
+ > **Scope:** dotnet
4
+ > **Layer:** 0 (resumo injetado pela rule `csharp-standards` (`**/*.cs`, `**/*.csproj`); o `stack-scan` acusa o projeto sem estes arquivos)
5
+ > **Keywords:** analyzer, roslyn, BannedApiAnalyzers, BannedSymbols, globalconfig, Guid.NewGuid, CreateVersion7, CancellationToken, CA2016, RS0030
6
+ > **Load When:** projeto .NET sem `BannedSymbols.txt`, ou o build acusou RS0030/CA2016
7
+
8
+ **Verified against:** .NET SDK 10.0.402, Microsoft.CodeAnalysis.BannedApiAnalyzers 5.6.0 (sonda: `Guid.NewGuid()` → erro RS0030, token não repassado → erro CA2016, código conforme → 0 erros). Last-verified: 2026-10-18.
9
+
10
+ ---
11
+
12
+ Regra de código que tem motivo e nunca pode ser quebrada vira **erro de build**, não frase que o
13
+ agente relê a cada sessão. Estes três arquivos na raiz da solution bastam; nenhum `.csproj` muda.
14
+
15
+ ## Arquivos
16
+
17
+ `Directory.Build.props` (se já existe, some o `ItemGroup` ao dele):
18
+
19
+ ```xml
20
+ <Project>
21
+ <ItemGroup>
22
+ <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="5.6.0" PrivateAssets="all" />
23
+ <AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" />
24
+ <GlobalAnalyzerConfigFiles Include="$(MSBuildThisFileDirectory)morph.globalconfig" />
25
+ </ItemGroup>
26
+ </Project>
27
+ ```
28
+
29
+ Com Central Package Management (`Directory.Packages.props`), a versão vai lá e o
30
+ `PackageReference` fica sem `Version`.
31
+
32
+ `BannedSymbols.txt`:
33
+
34
+ ```text
35
+ M:System.Guid.NewGuid;Use Guid.CreateVersion7(): ordenado, nao fragmenta o indice da PK
36
+ ```
37
+
38
+ `morph.globalconfig`:
39
+
40
+ ```ini
41
+ is_global = true
42
+ dotnet_diagnostic.RS0030.severity = error
43
+ dotnet_diagnostic.CA2016.severity = error
44
+ ```
45
+
46
+ ## O que cada regra compra
47
+
48
+ | Diagnóstico | Bloqueia | Por quê |
49
+ |---|---|---|
50
+ | RS0030 | `Guid.NewGuid()` | UUID v4 é aleatório: PK nele fragmenta o índice B-tree do Postgres a cada insert; `Guid.CreateVersion7()` é ordenado no tempo |
51
+ | CA2016 | método que recebe `CancellationToken` e não o repassa | requisição cancelada continua segurando conexão do banco e chamada ao LLM |
52
+
53
+ API nova para banir entra como linha no `BannedSymbols.txt` (formato `M:`/`T:`/`P:` com a
54
+ mensagem depois do `;`), com o porquê na mensagem. Nunca uma regra nova em prosa.
55
+
56
+ ## O que não é regra
57
+
58
+ `sealed`, primary constructor, `var`, expression-bodied member e ordem de `using` são gosto: nem
59
+ prosa nem analyzer. Siga o arquivo vizinho, e o formatter do projeto decide a forma.
@@ -11,7 +11,7 @@
11
11
 
12
12
  ## Overview
13
13
 
14
- Each feature in VSA is a self-contained slice with 5 files in a feature folder. Handlers implement `IHandler<TRequest, TResponse>` (not MediatR). Dependencies use primary constructors. The data path is `Handler -> ApplicationDbContext` — no service layer, no generic repository in between.
14
+ Each feature in VSA is a self-contained slice with 5 files in a feature folder. Handlers implement `IHandler<TRequest, TResponse>` (not MediatR). The data path is `Handler -> ApplicationDbContext` — no service layer, no generic repository in between.
15
15
 
16
16
  ---
17
17
 
@@ -108,11 +108,8 @@ public sealed class CreateBookHandler(
108
108
 
109
109
  ### Key Patterns
110
110
 
111
- - **`sealed class`** — handlers are never inherited
112
- - **`sealed record`** — DTOs are immutable value types
113
- - **Primary constructors** — `(ApplicationDbContext db)` replaces field + constructor boilerplate
114
- - **`Guid.CreateVersion7()`** — time-sortable, index-friendly UUID (not `Guid.NewGuid()`)
115
- - **`CancellationToken`** — passed through every async call
111
+ - **`record`** — DTOs are immutable value types
112
+ - **`Guid.CreateVersion7()` and a forwarded `CancellationToken`** — enforced by the build (`backend-dotnet-analyzers`), not by review
116
113
  - **`Result<T>` return** — never throw for expected failures
117
114
 
118
115
  ---
@@ -149,7 +146,7 @@ internal sealed class CreateBookEndpoint : IApiEndpoint
149
146
 
150
147
  ### Endpoint Conventions
151
148
 
152
- - **`internal sealed`** — endpoints are not public API
149
+ - **`internal`** — endpoints are not public API
153
150
  - **`IApiEndpoint`** — single interface for auto-discovery
154
151
  - **Handler injected via DI** — resolved as `IHandler<TReq, TRes>` parameter
155
152
  - **`Match()` for responses** — the success lambda receives the value (never read `.Value` directly); the failure lambda is `error.ToHttpResult()` from `Extensions/ErrorHttpMapping.cs` (`backend/dotnet/result-pattern.md`), which maps `ErrorType` to the status — never `Results.BadRequest` for every error
@@ -299,13 +296,9 @@ builder.Services.AddExceptionHandler<CustomExceptionHandler>()
299
296
  | Use MediatR `IRequest`/`Send()` | Use `IHandler<TReq, TRes>` resolved directly from DI |
300
297
  | Create a service/manager layer | Handler uses `ApplicationDbContext` directly |
301
298
  | Add a generic repository (`IRepository<T>`/`IUnitOfWork`) | `DbSet<T>` is the repository; `DbContext` is the unit of work |
302
- | Use `Guid.NewGuid()` | Use `Guid.CreateVersion7()` for time-sortable IDs |
303
- | Make handler classes non-sealed | Always `sealed class` |
304
- | Use mutable DTOs (class with setters) | Always `sealed record` with positional parameters |
305
- | Omit `CancellationToken` from async methods | Pass `CancellationToken` through every async call |
299
+ | Use mutable DTOs (class with setters) | Always `record` with positional parameters |
306
300
  | Put business logic in endpoints | Endpoints only map HTTP and call `Match()` |
307
301
  | Share handlers across features | Each feature has its own handler, even if similar |
308
- | Use constructor + field for DI | Use primary constructors: `class Handler(IDep dep)` |
309
302
  | Group by technical concern (Controllers/, Services/) | Group by feature: `Features/BookFeature/CreateBook/` |
310
303
  | Route data access through any indirection layer | Inject `DbContext` and write LINQ in the slice; dedicated store only with `plan.md` (`## Decisões`) justification |
311
304
  | Use raw SQL (`FromSqlRaw` / `ExecuteQuery(string)`) by default | Use LINQ; raw SQL only with a `plan.md` (`## Decisões`) justification |
@@ -36,7 +36,6 @@ framework/templates/
36
36
  │
37
37
  ├── integrations/ # asaas-client, asaas-webhook, neon-auth-config
38
38
  ├── ui/ # design-system.css
39
- ├── meta-prompts/ # fusion, squad-leaders, validators
40
39
  ├── examples/ # design-system-examples.md
41
40
  └── REGISTRY.json # template metadata — the index every consumer reads
42
41
  ```
@@ -642,213 +642,6 @@
642
642
  "isFragment": true,
643
643
  "validatePlaceholders": true
644
644
  },
645
- {
646
- "id": "hop-squad-leader",
647
- "kind": "template",
648
- "name": "Squad Leader HOP",
649
- "path": "meta-prompts/squad-leaders/squad-leader.md",
650
- "location": "framework",
651
- "category": "meta-prompts",
652
- "technology": "handlebars",
653
- "phase": "implement",
654
- "required": false,
655
- "description": "Generic squad leader higher-order prompt with mission, domain-leaders, standards, tasks, deliverables, constraints",
656
- "outputName": "squad-leader-prompt.md",
657
- "placeholders": [
658
- "AGENT_ID",
659
- "CONSTRAINTS",
660
- "DELIVERABLES",
661
- "DOMAIN",
662
- "DOMAIN_LEADERS",
663
- "FEATURE_NAME",
664
- "MISSION",
665
- "SPEC_SUMMARY",
666
- "STANDARDS",
667
- "TASKS"
668
- ],
669
- "stackSpecific": false,
670
- "engine": "handlebars",
671
- "deprecated": false,
672
- "hopType": "squad-leader",
673
- "validatePlaceholders": true
674
- },
675
- {
676
- "id": "hop-backend-squad",
677
- "kind": "template",
678
- "name": "Backend Squad Leader HOP",
679
- "path": "meta-prompts/squad-leaders/backend-squad.md",
680
- "location": "framework",
681
- "category": "meta-prompts",
682
- "technology": "handlebars",
683
- "phase": "implement",
684
- "required": false,
685
- "description": "Backend squad leader HOP for dotnet-senior with backend domain leaders, .NET 10 standards, architecture and security guidelines",
686
- "outputName": "backend-squad-prompt.md",
687
- "placeholders": [
688
- "ADDITIONAL_LEADERS",
689
- "AGENT_ID",
690
- "CONSTRAINTS",
691
- "DELIVERABLES",
692
- "FEATURE_NAME",
693
- "MISSION",
694
- "SPEC_SUMMARY",
695
- "TASKS"
696
- ],
697
- "stackSpecific": false,
698
- "engine": "handlebars",
699
- "deprecated": false,
700
- "hopType": "squad-leader",
701
- "applicableStacks": [
702
- "nextjs-neon"
703
- ],
704
- "validatePlaceholders": true
705
- },
706
- {
707
- "id": "hop-frontend-squad",
708
- "kind": "template",
709
- "name": "Frontend Squad Leader HOP",
710
- "path": "meta-prompts/squad-leaders/frontend-squad.md",
711
- "location": "framework",
712
- "category": "meta-prompts",
713
- "technology": "handlebars",
714
- "phase": "implement",
715
- "required": false,
716
- "description": "Frontend squad leader HOP for ui-designer with Next.js domain leaders and design system standards",
717
- "outputName": "frontend-squad-prompt.md",
718
- "placeholders": [
719
- "ADDITIONAL_LEADERS",
720
- "AGENT_ID",
721
- "CONSTRAINTS",
722
- "DELIVERABLES",
723
- "DOMAIN",
724
- "FEATURE_NAME",
725
- "MISSION",
726
- "SPEC_SUMMARY",
727
- "TASKS"
728
- ],
729
- "stackSpecific": false,
730
- "engine": "handlebars",
731
- "deprecated": false,
732
- "hopType": "squad-leader",
733
- "applicableStacks": [
734
- "nextjs-neon"
735
- ],
736
- "validatePlaceholders": true
737
- },
738
- {
739
- "id": "hop-checkpoint-validator",
740
- "kind": "template",
741
- "name": "Checkpoint Validator HOP",
742
- "path": "meta-prompts/validators/checkpoint-validator.md",
743
- "location": "framework",
744
- "category": "meta-prompts",
745
- "technology": "handlebars",
746
- "phase": "implement",
747
- "required": false,
748
- "description": "Checkpoint validator HOP running architecture, security, design-system, and packages validators with pass/fail decision logic",
749
- "outputName": "checkpoint-validator-prompt.md",
750
- "placeholders": [
751
- "CHECKPOINT_FREQUENCY",
752
- "CHECKPOINT_NUM",
753
- "FEATURE_NAME",
754
- "FILES_CHANGED",
755
- "RECENT_TASKS",
756
- "TASKS_COMPLETED",
757
- "TASKS_SINCE_LAST",
758
- "TASKS_TOTAL"
759
- ],
760
- "stackSpecific": false,
761
- "engine": "handlebars",
762
- "deprecated": false,
763
- "hopType": "validator",
764
- "validatePlaceholders": true
765
- },
766
- {
767
- "id": "hop-pre-commit-validator",
768
- "kind": "template",
769
- "name": "Pre-Commit Validator HOP",
770
- "path": "meta-prompts/validators/pre-commit-validator.md",
771
- "location": "framework",
772
- "category": "meta-prompts",
773
- "technology": "handlebars",
774
- "phase": "implement",
775
- "required": false,
776
- "description": "Pre-commit hook validator HOP checking conventional commits, secrets, agent spec validity, and stack-specific rules",
777
- "outputName": "pre-commit-validator-prompt.md",
778
- "placeholders": [
779
- "BRANCH_NAME",
780
- "FEATURE_NAME",
781
- "STAGED_FILES",
782
- "STAGED_FILE_COUNT",
783
- "TODO"
784
- ],
785
- "stackSpecific": false,
786
- "engine": "handlebars",
787
- "deprecated": false,
788
- "hopType": "validator",
789
- "validatePlaceholders": false
790
- },
791
- {
792
- "id": "hop-fusion-agent",
793
- "kind": "template",
794
- "name": "Fusion Agent HOP",
795
- "path": "meta-prompts/fusion/fusion-agent.md",
796
- "location": "framework",
797
- "category": "meta-prompts",
798
- "technology": "handlebars",
799
- "phase": "implement",
800
- "required": false,
801
- "description": "F-Thread fusion participant HOP with approach hints per thread index (standard, alternative, optimized) and self-assessment scoring",
802
- "outputName": "fusion-agent-prompt.md",
803
- "placeholders": [
804
- "AGENT_ID",
805
- "DELIVERABLES",
806
- "FUSION_STRATEGY",
807
- "SPEC_SUMMARY",
808
- "STANDARDS",
809
- "TASK_DESCRIPTION",
810
- "THREAD_COUNT",
811
- "THREAD_COUNT_MINUS_ONE",
812
- "THREAD_ID",
813
- "THREAD_INDEX"
814
- ],
815
- "stackSpecific": false,
816
- "engine": "handlebars",
817
- "deprecated": false,
818
- "hopType": "fusion",
819
- "validatePlaceholders": true
820
- },
821
- {
822
- "id": "hop-fusion-aggregator",
823
- "kind": "template",
824
- "name": "Fusion Aggregator HOP",
825
- "path": "meta-prompts/fusion/fusion-aggregator.md",
826
- "location": "framework",
827
- "category": "meta-prompts",
828
- "technology": "handlebars",
829
- "phase": "implement",
830
- "required": false,
831
- "description": "F-Thread aggregator HOP that scores and selects the winning implementation using best-of-n, consensus, or manual-select strategies",
832
- "outputName": "fusion-aggregator-prompt.md",
833
- "placeholders": [
834
- "AGENT_1",
835
- "AGENT_2",
836
- "AGENT_3",
837
- "FEATURE_NAME",
838
- "FUSION_RESULTS",
839
- "FUSION_STRATEGY",
840
- "SESSION_ID",
841
- "THREAD_COUNT",
842
- "WINNING_AGENT",
843
- "WINNING_SCORE",
844
- "WINNING_THREAD"
845
- ],
846
- "stackSpecific": false,
847
- "engine": "handlebars",
848
- "deprecated": false,
849
- "hopType": "fusion",
850
- "validatePlaceholders": true
851
- },
852
645
  {
853
646
  "id": "dotnet-abstractions-handler",
854
647
  "kind": "fragment",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "9.3.0",
3
+ "version": "9.4.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -743,7 +743,7 @@ export async function clearRootWorktreeRegistration(root, feature, { stamp } = {
743
743
  let unchanged = true;
744
744
  try { execFileSync('git', ['diff', '--quiet', 'HEAD', '--', featPathspec], gitOpts); } catch { unchanged = false; }
745
745
  if (!unchanged) {
746
- execFileSync('git', ['commit', '-m', `morph-spec finish: clear ${feature} worktree registration`, '--', featPathspec], gitOpts);
746
+ execFileSync('git', ['commit', '-m', `chore(morph): clear ${feature} worktree registration`, '--', featPathspec], gitOpts);
747
747
  }
748
748
  } catch { /* fail-open — a dirty root is a nuisance, not a reason to fail */ }
749
749
  return { shedBackup: null };
@@ -123,7 +123,7 @@ export function runArchive(feature, opts = {}) {
123
123
  ...(adrIndex.changed ? [ADR_INDEX_PATHSPEC] : []),
124
124
  ];
125
125
  try {
126
- execFileSync('git', ['commit', '-m', `morph-spec archive: ${feature}`, '--', ...pathspecs], { cwd, stdio: 'pipe' });
126
+ execFileSync('git', ['commit', '-m', `chore(morph): archive ${feature}`, '--', ...pathspecs], { cwd, stdio: 'pipe' });
127
127
  committed = true;
128
128
  } catch { /* nothing to commit / no identity — not fatal, the move + stage stand */ }
129
129
  }
@@ -494,7 +494,7 @@ export function finishPreflight({ feature, root, worktreePath, cwd, mode, branch
494
494
  }
495
495
 
496
496
  async function integrateMerge({ emit, root, feature, expectedBranch, defaultBranch, isWorktreeFlow, worktreePath, options, arch, backupStamp }) {
497
- const mergeMsg = `morph-spec finish: merge ${expectedBranch}`;
497
+ const mergeMsg = `chore(morph): merge ${expectedBranch}`;
498
498
  const adrNote = adrIndexNote(arch);
499
499
 
500
500
  if (isWorktreeFlow) {
@@ -36,7 +36,7 @@ export function clearRegistryCache() {
36
36
  _registryCache.clear();
37
37
  }
38
38
 
39
- export function getAllTemplates(projectPath = null) {
39
+ function getAllTemplates(projectPath = null) {
40
40
  const registry = loadTemplateRegistry(projectPath);
41
41
  return registry.templates || [];
42
42
  }
@@ -46,48 +46,6 @@ export function getTemplateById(templateId, projectPath = null) {
46
46
  return templates.find(t => t.id === templateId) || null;
47
47
  }
48
48
 
49
- export function getTemplatesByCategory(category, projectPath = null) {
50
- const templates = getAllTemplates(projectPath);
51
- return templates.filter(t => t.category === category);
52
- }
53
-
54
- export function getTemplatesByStack(stackId, projectPath = null) {
55
- const templates = getAllTemplates(projectPath);
56
- return templates.filter(t => {
57
- if (!t.stackSpecific) return true;
58
-
59
- return t.stacks && t.stacks.includes(stackId);
60
- });
61
- }
62
-
63
- export function getRequiredTemplates(stackId = null, projectPath = null) {
64
- let templates = getAllTemplates(projectPath);
65
-
66
- templates = templates.filter(t => t.required === true);
67
-
68
- if (stackId) {
69
- templates = templates.filter(t => {
70
- if (!t.stackSpecific) return true;
71
- return t.stacks && t.stacks.includes(stackId);
72
- });
73
- }
74
-
75
- return templates;
76
- }
77
-
78
- export function searchTemplates(keyword, projectPath = null) {
79
- const templates = getAllTemplates(projectPath);
80
- const lowerKeyword = keyword.toLowerCase();
81
-
82
- return templates.filter(t => {
83
- return (
84
- t.id.toLowerCase().includes(lowerKeyword) ||
85
- t.name.toLowerCase().includes(lowerKeyword) ||
86
- (t.description && t.description.toLowerCase().includes(lowerKeyword))
87
- );
88
- });
89
- }
90
-
91
49
  const FRAMEWORK_TEMPLATES_DIR = join(__dirname, '../../../framework/templates');
92
50
 
93
51
  /** Path inside this package's `framework/templates/`, not a project's `.morph/`; throws when missing. */
@@ -135,12 +135,10 @@ function auditDotnet(files, read) {
135
135
  if (serviceLayer.length > 0) add('Alto', 'Camada de Service/Manager entre handler e dados', serviceLayer, 'o handler fala com o `ApplicationDbContext` direto');
136
136
  const rawSql = grepFiles(source, read, /\b(FromSqlRaw|ExecuteSqlRaw|SqlQueryRaw|ExecuteQuery)\s*\(/);
137
137
  if (rawSql.length > 0) add('Alto', 'SQL cru', rawSql, 'LINQ, ou store dedicado com justificativa em ADR');
138
- const notSealed = source.filter((f) => {
139
- const text = read(f) ?? '';
140
- return [...text.matchAll(/^\s*(?:public|internal)\s+((?:\w+\s+)*)class\s+\w+/gm)]
141
- .some((m) => !/\b(sealed|abstract|static|partial)\b/.test(m[1]));
142
- });
143
- if (notSealed.length > 0) add('Médio', 'Classes não-sealed', notSealed, '`sealed` por padrão');
138
+ if (projects.length > 0 && !packages.has('Microsoft.CodeAnalysis.BannedApiAnalyzers')) {
139
+ add('Médio', 'Analyzers do stack ausentes (o build não barra `Guid.NewGuid()` nem token não repassado)', [],
140
+ '`Directory.Build.props` + `BannedSymbols.txt` + `morph.globalconfig` na raiz: `morph-spec standards --show backend-dotnet-analyzers#files`');
141
+ }
144
142
  const controllers = grepFiles(source, read, /:\s*Controller(Base)?\b/);
145
143
  rows.push({ layer: 'Estrutura', detected: controllers.length > 0 ? `${controllers.length} controller(s) MVC` : 'sem controllers', expected: 'VSA (endpoint por slice)', ok: controllers.length === 0 });
146
144
  return { rows, deviations };
@@ -289,7 +289,7 @@ export const DISPATCH_CONTRACT = [
289
289
  'Grave a saída em arquivo (`… > .morph/logs/test-last.log 2>&1`, sem nada depois) e leia o arquivo — um pipe troca o exit do teste pelo do último comando. ' +
290
290
  'Em batch paralelo SEM isolamento, só compile. Com `isolation: "worktree"`, o mesmo vale no seu worktree, e o último ato é o commit na branch dele.',
291
291
  '4. ESCOPO: uma fatia, ~4 arquivos (nunca mais de 8). Escopo maior falha e volta — fatie antes, não depois.',
292
- '5. SOBREVIVÊNCIA: o limite de turnos corta sem aviso: commite `wip(F{n})` a cada marco (um arquivo ou uma fatia coerente escrita), não só ao fim — ' +
292
+ '5. SOBREVIVÊNCIA: o limite de turnos corta sem aviso: commite `chore(F{n}): wip …` a cada marco (um arquivo ou uma fatia escrita), não só ao fim — ' +
293
293
  'e só os seus arquivos (`git add <paths>`, nunca `-A` nem diretório). Sem commit, a retomada recomeça do zero.',
294
294
  '6. PROCESSOS: NUNCA `taskkill /IM node.exe` (derruba os MCPs de todas as sessões). Encerre dev server pelo PID que você mesmo subiu. ' +
295
295
  'Build e suíte pesados rodam por `morph-spec heavy run -- <comando>`, em primeiro plano. Exit 3 é fila cheia depois da espera da própria fila: ' +
@@ -64,7 +64,7 @@ const MORPH_HOOKS = [
64
64
  hooks: [{
65
65
  type: 'command',
66
66
  command: 'node framework/hooks/claude-code/core/chain.js',
67
- args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'warn-bash-source-write', 'heavy-gate', 'worktree-env-inject']
67
+ args: ['pre-bash-denylist', 'conventional-commit', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'warn-bash-source-write', 'heavy-gate', 'worktree-env-inject']
68
68
  }]
69
69
  },
70
70