@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.
- package/README.md +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +6 -4
- package/framework/docs/CLI.md +1 -1
- package/framework/evals/review-checklist.md +1 -1
- package/framework/hooks/README.md +1 -0
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -15
- package/framework/hooks/claude-code/core/conventional-commit.js +57 -0
- package/framework/hooks/shared/command-tokens.js +17 -0
- package/framework/rules/csharp-standards.md +5 -3
- package/framework/rules/dotnet-templates.md +2 -2
- package/framework/rules/nextjs-standards.md +1 -0
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +3 -3
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +55 -8
- package/framework/standards/STANDARDS.meta.json +28 -0
- package/framework/standards/architecture/engineering-lens.md +100 -0
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +7 -9
- package/framework/standards/backend/dotnet/analyzers.md +59 -0
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -12
- package/framework/templates/README.md +0 -1
- package/framework/templates/REGISTRY.json +0 -207
- package/package.json +1 -1
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/archive.js +1 -1
- package/src/commands/state/finish.js +1 -1
- package/src/core/templates/template-registry.js +1 -43
- package/src/lib/stack/stack-scan.js +4 -6
- package/src/utils/agents-installer.js +1 -1
- package/src/utils/hooks-installer.js +1 -1
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +0 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +0 -100
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +0 -90
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +0 -84
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +0 -44
- package/framework/templates/meta-prompts/validators/checkpoint-validator.md +0 -107
- 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}`
|
|
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. **`
|
|
391
|
-
3.
|
|
392
|
-
4. **`
|
|
393
|
-
5.
|
|
394
|
-
6. **`
|
|
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
|
|
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).
|
|
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
|
-
- **`
|
|
112
|
-
- **`
|
|
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
|
|
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
|
|
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
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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 `
|
|
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
|
|