@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
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
|
|
4
4
|
|
|
5
5
|
**Package:** `@polymorphism-tech/morph-spec`
|
|
6
|
-
**Version:** 9.
|
|
6
|
+
**Version:** 9.4.0
|
|
7
7
|
**Requires:** Node.js 22+, Claude Code
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -380,4 +380,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
|
|
|
380
380
|
|
|
381
381
|
---
|
|
382
382
|
|
|
383
|
-
*morph-spec v9.
|
|
383
|
+
*morph-spec v9.4.0 by [Polymorphism Tech](https://polymorphism.tech)*
|
package/claude-plugin.json
CHANGED
package/docs/CHEATSHEET.md
CHANGED
package/docs/QUICKSTART.md
CHANGED
package/framework/agents.json
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"model": "sonnet",
|
|
50
50
|
"maxTurns": 120,
|
|
51
51
|
"effort": "medium",
|
|
52
|
-
"persona": "Você é um engenheiro .NET sênior. Pensa em VSA com handlers diretos, result pattern
|
|
52
|
+
"persona": "Você é um engenheiro .NET sênior. Pensa em VSA com handlers diretos, result pattern. Acesso a dados: ApplicationDbContext injetado direto no slice (DbSet<T> já é repository, DbContext já é unit of work); para concorrência ou SQL específico, store dedicado hand-written com justificativa em ADR no plan.md; nunca uma camada de Service/Manager; nunca repository genérico (IRepository<T>/IUnitOfWork). Não usa exceptions como fluxo. Consulta o standard antes de escrever cada slice. Nomeia tudo em PascalCase para tipos, camelCase para parâmetros. REUSO ANTES DE CRIAR: antes de declarar record/class/interface novo, rode `morph-spec graph explain <Nome>` e `graph affected <Conceito> --uses-only` (sem grafo: `grep -rn`); se já existe algo que cobre o caso, reuse ou estenda, e se criar mesmo assim diga por que em uma linha no relatório da fatia. COMENTÁRIO E TESTE: nenhum comentário, mesmo com vizinho comentado: nome, tipo e teste explicam (pós-condição no nome do teste); só o porquê invisível, sem `///`/JSDoc, id de cenário/fatia ou banner; teste só no Cn `prova: teste` (um por cenário, tabela); nenhum que só fixa texto ou DTO."
|
|
53
53
|
},
|
|
54
54
|
"ef-modeler": {
|
|
55
55
|
"title": "EF Core / Database Modeler",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"model": "sonnet",
|
|
91
91
|
"maxTurns": 120,
|
|
92
92
|
"effort": "medium",
|
|
93
|
-
"persona": "Você é um especialista em EF Core sobre Neon (Postgres). Pensa em migrations explícitas, configurações fluentes, navigation properties bem nomeadas, índices intencionais.
|
|
93
|
+
"persona": "Você é um especialista em EF Core sobre Neon (Postgres). Pensa em migrations explícitas, configurações fluentes, navigation properties bem nomeadas, índices intencionais. Nunca leak de DbContext fora do handler. REUSO ANTES DE CRIAR: antes de declarar entidade, configuração ou value object novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`) — entidade duplicada vira migration divergente; se criar mesmo assim, justifique em uma linha no relatório da fatia. COMENTÁRIO E TESTE: nenhum comentário, mesmo com vizinho comentado: nome, tipo e teste explicam (pós-condição no nome do teste); só o porquê invisível, sem `///`/JSDoc, id de cenário/fatia ou banner; teste só no Cn `prova: teste` (um por cenário, tabela); nenhum que só fixa texto ou DTO."
|
|
94
94
|
},
|
|
95
95
|
"maf-expert": {
|
|
96
96
|
"title": "Microsoft Agent Framework Expert",
|
|
@@ -390,14 +390,16 @@
|
|
|
390
390
|
"stack": [
|
|
391
391
|
"*"
|
|
392
392
|
],
|
|
393
|
-
"standards": [
|
|
393
|
+
"standards": [
|
|
394
|
+
"architecture-engineering-lens"
|
|
395
|
+
],
|
|
394
396
|
"tools": "Read, Glob, Grep, Bash",
|
|
395
397
|
"mcp": [
|
|
396
398
|
"graphify"
|
|
397
399
|
],
|
|
398
400
|
"model": "sonnet",
|
|
399
401
|
"maxTurns": 60,
|
|
400
|
-
"persona": "Você é o revisor independente do Gate 3, read-only. Não participou da implementação e não prova cenários (é o verificador). Você lê SÓ o commit revisado: `git diff {base}..{sha}`, `git show {sha}:<caminho>`, `git log {base}..{sha}` — nunca a árvore de trabalho (Read/Glob/Grep sobre `src/` leem o disco, onde o verificador pode estar com um mutante aplicado). `base` e `sha` vêm no dispatch. Julga o diff em cinco dimensões, na seção \"Revisor\" de `.morph/framework/evals/review-checklist.md`: ESCOPO (o diff contra as fatias e o `## Contrato de implementação` do plano — arquivo fora de toda fatia, comportamento que nenhum cenário pediu, teste além do orçamento — teste para Cn `prova: leitura`, mais de um por Cn de risco sem porquê no plano, teste que só fixa texto/constante/DTO; `expectedCount` menor no `config.json` sem teste apagado no mesmo diff é `regressao`); ARQUITETURA (padrão ou abstração nova contra o stack do projeto e os standards — camada que o projeto não usa, repositório sobre o DbContext, dependência nova sem decisão no plano); REGRESSÃO (para cada símbolo público alterado, `morph-spec graph affected <símbolo> --prod --json`; sem grafo — `no-graph`, `not-found`, `ambiguous` — caia para `git grep -n <símbolo> {sha}` e siga, a ausência de grafo nunca bloqueia; ausência de consumidor nunca sai do grafo); O QUE A CORREÇÃO ABRIU (revisando uma correção de rodada anterior, releia o comportamento vizinho); PERFORMANCE só com evidência (lista sem paginação no servidor sobre volume real, consulta N+1 visível no diff) e RASTREABILIDADE de chamada a LLM (dá para reconstruir o que foi ao provedor e o que voltou?). Tipos de bloqueador: `escopo`, `arquitetura`, `regressao`, `performance`. Seu default é SHIP: dúvida vira nota. Arquivo do framework ou da feature (o checklist, o plan.md) você lê com Read, nunca `sed`/`cat`/`head`; código do commit, com `git show {sha}:<caminho>`. NUNCA modifique código, plan.md, checks.json ou feature.json, nunca `git checkout --`/`git stash`, nada em segundo plano nem laço de espera (`sleep`, `until`)."
|
|
402
|
+
"persona": "Você é o revisor independente do Gate 3, read-only. Leia como o arquiteto que herda este repo amanhã: simples, legível, fácil de estender. Não participou da implementação e não prova cenários (é o verificador). Você lê SÓ o commit revisado: `git diff {base}..{sha}`, `git show {sha}:<caminho>`, `git log {base}..{sha}` — nunca a árvore de trabalho (Read/Glob/Grep sobre `src/` leem o disco, onde o verificador pode estar com um mutante aplicado). `base` e `sha` vêm no dispatch. Julga o diff em cinco dimensões, na seção \"Revisor\" de `.morph/framework/evals/review-checklist.md`: ESCOPO (o diff contra as fatias e o `## Contrato de implementação` do plano — arquivo fora de toda fatia, comportamento que nenhum cenário pediu, teste além do orçamento — teste para Cn `prova: leitura`, mais de um por Cn de risco sem porquê no plano, teste que só fixa texto/constante/DTO; `expectedCount` menor no `config.json` sem teste apagado no mesmo diff é `regressao`); ARQUITETURA (padrão ou abstração nova contra o stack do projeto e os standards — camada que o projeto não usa, repositório sobre o DbContext, dependência nova sem decisão no plano — e o que o DIFF introduziu da lista fechada `architecture-engineering-lens#review-blockers`; o que já existia no arquivo é nota); REGRESSÃO (para cada símbolo público alterado, `morph-spec graph affected <símbolo> --prod --json`; sem grafo — `no-graph`, `not-found`, `ambiguous` — caia para `git grep -n <símbolo> {sha}` e siga, a ausência de grafo nunca bloqueia; ausência de consumidor nunca sai do grafo); O QUE A CORREÇÃO ABRIU (revisando uma correção de rodada anterior, releia o comportamento vizinho); PERFORMANCE só com evidência (lista sem paginação no servidor sobre volume real, consulta N+1 visível no diff) e RASTREABILIDADE de chamada a LLM (dá para reconstruir o que foi ao provedor e o que voltou?). Tipos de bloqueador: `escopo`, `arquitetura`, `regressao`, `performance`. Seu default é SHIP: dúvida vira nota. Arquivo do framework ou da feature (o checklist, o plan.md) você lê com Read, nunca `sed`/`cat`/`head`; código do commit, com `git show {sha}:<caminho>`. NUNCA modifique código, plan.md, checks.json ou feature.json, nunca `git checkout --`/`git stash`, nada em segundo plano nem laço de espera (`sleep`, `until`)."
|
|
401
403
|
},
|
|
402
404
|
"security": {
|
|
403
405
|
"title": "Gate 3 Security Reviewer",
|
package/framework/docs/CLI.md
CHANGED
|
@@ -500,7 +500,7 @@ Sem fatia na linha de comando, a saída traz `reviews: { verifier, reviewer, sec
|
|
|
500
500
|
|
|
501
501
|
### stack-scan
|
|
502
502
|
|
|
503
|
-
**`morph-spec stack-scan [<feature>] [--json]`** — varredura determinística do código contra `config.project.stack`: só os stacks configurados são julgados (sem `project.stack`, nenhuma regra roda e o resultado é `não auditado: project.stack ausente em .morph/config/config.json`, nunca "Em compliance" — exit 0; stack sem regras de varredura sai `não auditado` pelo mesmo motivo; código de um stack não configurado é só anotado). `dotnet`: TargetFramework abaixo de net10 e ORM/banco diferente (Dapper puro, SQL Server, MySQL) são **Bloqueante**; `Guid.NewGuid()`, repository genérico (`IRepository<T>`/`IUnitOfWork` — código morto: apagar a pasta `Repository/` e as 2 linhas de DI), classe `*Service`/`*Manager` sobre `DbContext` e SQL cru (`FromSqlRaw`, `ExecuteSqlRaw`, `SqlQueryRaw`, `ExecuteQuery`), Semantic Kernel (pacote `Microsoft.SemanticKernel*` ou `[KernelFunction]` no código) e serviço de agente hospedado (`Azure.AI.Projects`, `Azure.AI.Agents.Persistent`) são **Alto** — os dois últimos são linhas da tabela de anti-padrões do `ai-agents-setup` §7, para a qual a migração aponta;
|
|
503
|
+
**`morph-spec stack-scan [<feature>] [--json]`** — varredura determinística do código contra `config.project.stack`: só os stacks configurados são julgados (sem `project.stack`, nenhuma regra roda e o resultado é `não auditado: project.stack ausente em .morph/config/config.json`, nunca "Em compliance" — exit 0; stack sem regras de varredura sai `não auditado` pelo mesmo motivo; código de um stack não configurado é só anotado). `dotnet`: TargetFramework abaixo de net10 e ORM/banco diferente (Dapper puro, SQL Server, MySQL) são **Bloqueante**; `Guid.NewGuid()`, repository genérico (`IRepository<T>`/`IUnitOfWork` — código morto: apagar a pasta `Repository/` e as 2 linhas de DI), classe `*Service`/`*Manager` sobre `DbContext` e SQL cru (`FromSqlRaw`, `ExecuteSqlRaw`, `SqlQueryRaw`, `ExecuteQuery`), Semantic Kernel (pacote `Microsoft.SemanticKernel*` ou `[KernelFunction]` no código) e serviço de agente hospedado (`Azure.AI.Projects`, `Azure.AI.Agents.Persistent`) são **Alto** — os dois últimos são linhas da tabela de anti-padrões do `ai-agents-setup` §7, para a qual a migração aponta; projeto .NET sem `Microsoft.CodeAnalysis.BannedApiAnalyzers` (migração: `backend-dotnet-analyzers`) e SDK de IA (`Microsoft.Agents.AI*`, `Microsoft.Extensions.AI*`, `OpenAI`, `Google.GenAI`, `ModelContextProtocol`) com versão flutuante (`*` ou faixa) são **Médio**; pacote `Microsoft.EntityFrameworkCore|AspNetCore|Extensions` abaixo da major 10 é **Baixo**. `nextjs`: frontend sem Next.js é Bloqueante; Pages Router sem `app/` e biblioteca de UI diferente (MUI, antd, Chakra, Bootstrap, Mantine) são Médio. Código de teste não conta; `node_modules`, `bin`, `obj`, `.morph`, `.claude` e os contêineres de worktree ficam fora; arquivo ilegível é anotado, nunca derruba. Sem código fonte = GREENFIELD. As contagens são de texto e superestimam consumidores — o relatório aponta `graph affected <símbolo> --uses-only`. Desvio Bloqueante/Alto vira nó da árvore de decisões da `morph-plan`. Com `<feature>`, grava `.morph/features/<f>/2-plan/stack-scan.md` (exit 2 se a feature não existe, 1 fora da árvore dona); sem, imprime o markdown.
|
|
504
504
|
|
|
505
505
|
### tests-audit
|
|
506
506
|
|
|
@@ -34,7 +34,7 @@ diferente") é nota; nota não bloqueia e não pede rodada nova. Na dúvida, sem
|
|
|
34
34
|
| `teste` | verificador | teste vermelho; teste que não morde a regra que diz proteger | saída do verify, mutante sobrevivente, assert que passa pelo motivo errado |
|
|
35
35
|
| `regressao` | revisor | comportamento que funcionava e quebrou | consumidor afetado + o caminho que quebra |
|
|
36
36
|
| `escopo` | revisor | o diff faz o que nenhuma fatia pediu, ou deixa de fazer o que o contrato pede | arquivo/trecho fora das fatias, ou o item do contrato ausente |
|
|
37
|
-
| `arquitetura` | revisor | padrão ou abstração nova contra o stack e os standards | o trecho e o standard que ele contraria |
|
|
37
|
+
| `arquitetura` | revisor | padrão ou abstração nova contra o stack e os standards; item da lista fechada de `architecture-engineering-lens#review-blockers` que o DIFF introduziu (o que já existia é nota) | o trecho e o standard que ele contraria |
|
|
38
38
|
| `performance` | revisor | custo que o volume real torna visível | a consulta/lista e o volume que a derruba |
|
|
39
39
|
| `seguranca` | segurança | authz, segredo, injeção, PII, custo sem teto, migration destrutiva | o caminho de exploração |
|
|
40
40
|
|
|
@@ -25,6 +25,7 @@ typechecks or runs tests: that is `morph-spec verify`.
|
|
|
25
25
|
| **PreToolUse** (Write\|Edit) | protect-spec-files.js | Block | Blocks edits to `plan.md` (and the uiux artifacts) after their gate is approved; an append-only `## Errata` block at the end of `plan.md` passes |
|
|
26
26
|
| **PreToolUse** (Write\|Edit) | enforce-phase-writes.js | Block | Ensures writes go to current phase directory |
|
|
27
27
|
| **PreToolUse** (Bash) | pre-bash-denylist.js | Block | Blocks `rm -rf .morph/` and direct state edits |
|
|
28
|
+
| **PreToolUse** (Bash) | conventional-commit.js | Block | Blocks a `git commit -m` whose subject is not Conventional Commits (`<type>(<scope>): <subject>`); a `$(…)`/heredoc message, `-F` and git-generated `Merge`/`Revert` pass |
|
|
28
29
|
| **PreToolUse** (Bash) | block-destructive-worktree-removal.js | Block | Blocks `git worktree remove` / `rm -rf` / `Remove-Item -Recurse` on a directory holding reparse points (the infra junctions a raw removal would FOLLOW out, emptying the root's `.claude/`); names `morph-spec worktree remove` |
|
|
29
30
|
| **PreToolUse** (Bash) | block-raw-worktree-add.js | Block | Blocks raw `git worktree add` (a tree with no junctions → no hooks, no port block, no identity, outside `worktrees/`); names `morph-spec worktree setup <feature>` / `worktree provision <name> --base <ref>`. `list`/`prune`/`remove`/`lock`/`move`/`repair` pass |
|
|
30
31
|
| **PreToolUse** (Bash) | warn-bash-source-write.js | Warn | Heredoc/redirect/`tee` into project source (`isProjectSource`): context naming the file, the command still runs — Write/Edit is what phase, protected-file and comment guards see |
|
|
@@ -3,23 +3,10 @@ import { blockResult, runStandalone } from '../../shared/hook-response.js';
|
|
|
3
3
|
import { stateExists } from '../../shared/state-reader.js';
|
|
4
4
|
// From shared/, never from the sibling hook: importing a hook module fires its
|
|
5
5
|
// standalone tail (stdin read + exit) inside THIS process.
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const GIT_GLOBAL_WITH_VALUE = new Set(['-c', '-C', '--git-dir', '--work-tree', '--namespace', '--exec-path', '--config-env']);
|
|
6
|
+
import { gitInvocations } from '../../shared/command-tokens.js';
|
|
9
7
|
|
|
10
8
|
export function isRawWorktreeAdd(command) {
|
|
11
|
-
|
|
12
|
-
const lower = tokens.map((t) => t.toLowerCase());
|
|
13
|
-
for (let i = 0; i < lower.length; i++) {
|
|
14
|
-
if (lower[i] !== 'git' && !/(^|[\\/])git(\.exe)?$/.test(lower[i])) continue;
|
|
15
|
-
let j = i + 1;
|
|
16
|
-
while (j < lower.length && lower[j].startsWith('-')) {
|
|
17
|
-
if (GIT_GLOBAL_WITH_VALUE.has(tokens[j])) j += 2;
|
|
18
|
-
else j += 1;
|
|
19
|
-
}
|
|
20
|
-
if (lower[j] === 'worktree' && lower[j + 1] === 'add') return true;
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
9
|
+
return gitInvocations(command).some(({ sub, args }) => sub === 'worktree' && args[0]?.toLowerCase() === 'add');
|
|
23
10
|
}
|
|
24
11
|
|
|
25
12
|
export async function run(payload) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { blockResult, runStandalone } from '../../shared/hook-response.js';
|
|
3
|
+
import { stateExists } from '../../shared/state-reader.js';
|
|
4
|
+
import { gitInvocations } from '../../shared/command-tokens.js';
|
|
5
|
+
|
|
6
|
+
const CONVENTIONAL = /^(feat|fix|refactor|test|chore|docs|perf|ci|build|style|revert)(\([^)]+\))?!?: \S/;
|
|
7
|
+
const GIT_GENERATED = /^(Merge|Revert) /;
|
|
8
|
+
const MESSAGE_FLAG = /^-[a-zA-Z]*m$/;
|
|
9
|
+
|
|
10
|
+
function subjectOf(args) {
|
|
11
|
+
for (let i = 0; i < args.length; i++) {
|
|
12
|
+
const arg = args[i];
|
|
13
|
+
if (arg === '--message' || MESSAGE_FLAG.test(arg)) return args[i + 1] ?? null;
|
|
14
|
+
if (arg.startsWith('--message=')) return arg.slice('--message='.length);
|
|
15
|
+
if (/^-m./.test(arg)) return arg.slice(2);
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// `$(…)` and heredocs are only known after the shell expands them.
|
|
21
|
+
function isJudgeable(subject) {
|
|
22
|
+
return subject !== null && !subject.includes('$(') && !subject.includes('<<') && !subject.includes('`');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function nonConventionalSubject(command) {
|
|
26
|
+
for (const { sub, args } of gitInvocations(command)) {
|
|
27
|
+
if (sub !== 'commit') continue;
|
|
28
|
+
const subject = subjectOf(args);
|
|
29
|
+
if (!isJudgeable(subject)) continue;
|
|
30
|
+
const firstLine = subject.split('\n')[0].trim();
|
|
31
|
+
if (!CONVENTIONAL.test(firstLine) && !GIT_GENERATED.test(firstLine)) return firstLine;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function run(payload) {
|
|
37
|
+
try {
|
|
38
|
+
if (!stateExists()) return null;
|
|
39
|
+
|
|
40
|
+
const command = payload?.tool_input?.command || '';
|
|
41
|
+
if (!command.includes('commit')) return null;
|
|
42
|
+
const subject = nonConventionalSubject(command);
|
|
43
|
+
if (subject === null) return null;
|
|
44
|
+
|
|
45
|
+
return blockResult(
|
|
46
|
+
`Bloqueado: o assunto do commit não segue Conventional Commits: "${subject}".\n\n` +
|
|
47
|
+
'Formato: `<tipo>(<escopo>): <assunto>` — tipo em feat, fix, refactor, test, chore ' +
|
|
48
|
+
'(também docs, perf, ci, build, style, revert); `!` antes de `:` marca breaking change.\n' +
|
|
49
|
+
'Exemplos: `feat(orders): aceita cupom no checkout`, `fix: trata pedido sem itens`, ' +
|
|
50
|
+
'`chore(F2): wip handler de pagamento`.'
|
|
51
|
+
);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await runStandalone(run);
|
|
@@ -108,6 +108,23 @@ function simpleCommands(head) {
|
|
|
108
108
|
return commands.filter((c) => c.length > 0);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
const GIT_EXECUTABLE = /(^|[\\/])git(\.exe)?$/i;
|
|
112
|
+
const GIT_GLOBAL_WITH_VALUE = new Set(['-c', '-C', '--git-dir', '--work-tree', '--namespace', '--exec-path', '--config-env']);
|
|
113
|
+
|
|
114
|
+
/** Each `git <sub> …` the command runs, global options skipped, one `{ sub, args }` per simple command. */
|
|
115
|
+
export function gitInvocations(command) {
|
|
116
|
+
const invocations = [];
|
|
117
|
+
for (const tokens of simpleCommands(command)) {
|
|
118
|
+
const texts = tokens.map((t) => t.text);
|
|
119
|
+
const at = texts.findIndex((t) => GIT_EXECUTABLE.test(t));
|
|
120
|
+
if (at < 0) continue;
|
|
121
|
+
let j = at + 1;
|
|
122
|
+
while (j < texts.length && texts[j].startsWith('-')) j += GIT_GLOBAL_WITH_VALUE.has(texts[j]) ? 2 : 1;
|
|
123
|
+
if (j < texts.length) invocations.push({ sub: texts[j].toLowerCase(), args: texts.slice(j + 1) });
|
|
124
|
+
}
|
|
125
|
+
return invocations;
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
function positionals(args, takesValue) {
|
|
112
129
|
const out = [];
|
|
113
130
|
const values = [];
|
|
@@ -8,13 +8,15 @@ stacks:
|
|
|
8
8
|
|
|
9
9
|
# C# and .NET VSA Standards
|
|
10
10
|
|
|
11
|
-
- **architecture-vertical-slice-vertical-slice** (`architecture/vertical-slice/vertical-slice.md`) — Uma slice por operação em `Features/{Entity}Feature/{Verb}{Entity}/`: Request/Response `
|
|
12
|
-
- **backend-dotnet-vsa-handler-patterns** (`backend/dotnet/vsa-handler-patterns.md`) — Handler
|
|
11
|
+
- **architecture-vertical-slice-vertical-slice** (`architecture/vertical-slice/vertical-slice.md`) — Uma slice por operação em `Features/{Entity}Feature/{Verb}{Entity}/`: Request/Response `record`, Handler, Validator e Endpoint `IApiEndpoint`. Decorators Scrutor: Logging → Validation → Handler. PROIBIDO: MediatR/`IRequest`, AggregateRoot, DomainEvent, CQRS formal, camada Application Service, DTO compartilhado entre features, `IRepository<T>`/`IUnitOfWork`.
|
|
12
|
+
- **backend-dotnet-vsa-handler-patterns** (`backend/dotnet/vsa-handler-patterns.md`) — Handler implementando `IHandler<TRequest, TResponse>` (não MediatR): injeta `ApplicationDbContext` direto e devolve `Result<T>`. Erros em `{Feature}Errors.cs`. SQL cru ou store dedicado só com justificativa em `plan.md` (`## Decisões`).
|
|
13
13
|
- **backend-dotnet-result-pattern** (`backend/dotnet/result-pattern.md`) — Falha esperada é `Result`/`Result<T>`, nunca exceção. Endpoint: `result.Match(value => Results.Ok(value), error => error.ToHttpResult())`; nunca leia `.Value` fora do `Match`. Status pelo `ErrorType` (Validation 400, NotFound 404, Conflict 409, Unauthorized 401, Forbidden 403, Unexpected 500), nunca BadRequest para tudo. PROIBIDO: try/catch no handler, retornar null, erro string sem código.
|
|
14
|
-
- **backend-dotnet-fluent-validation-vsa** (`backend/dotnet/fluent-validation-vsa.md`) — Um `
|
|
14
|
+
- **backend-dotnet-fluent-validation-vsa** (`backend/dotnet/fluent-validation-vsa.md`) — Um `AbstractValidator<TRequest>` (FluentValidation 12) por request, na pasta da slice; o `ValidationDecorator` roda antes do handler e devolve `Result.Failure(ValidationError)`. `AddValidatorsFromAssembly()` ANTES de `AddHandlersFromAssembly()`. Sempre `.WithMessage()`; tempo via `TimeProvider` lido DENTRO da regra. Nunca lance `ValidationException`.
|
|
15
15
|
- **backend-dotnet-scrutor** (`backend/dotnet/scrutor.md`) — `AddHandlersFromAssembly` registra cada `IHandler<,>` como Scoped (filtro `!t.ContainsGenericParameters`) e depois `Decorate` Validation e então Logging (o último registrado é o mais externo). Endpoints `IApiEndpoint` via `TryAddEnumerable` Transient + `MapApiEndpoints`. Nunca registre handler um a um.
|
|
16
16
|
- **backend-dotnet-scalar-ui** (`backend/dotnet/scalar-ui.md`) — Scalar no lugar do Swagger: `Microsoft.AspNetCore.OpenApi` 10.0.0 + `Scalar.AspNetCore` 2.12.40, nunca Swashbuckle. `MapOpenApi()` só em `IsDevelopment()` e antes de `MapScalarApiReference`. Todo endpoint com `.WithTags(ApiTags.X)` (constantes), `.Produces<T>` e `.ProducesProblem(status)` por falha possível.
|
|
17
|
+
- **backend-dotnet-analyzers** (`backend/dotnet/analyzers.md`) — O build cobra, a prosa não repete: `Guid.NewGuid()` é erro RS0030 (use `Guid.CreateVersion7()`) e token recebido e não repassado é erro CA2016, por `Directory.Build.props` + `BannedSymbols.txt` + `morph.globalconfig` na raiz. `sealed`, primary constructor e `var` são gosto: siga o arquivo vizinho.
|
|
17
18
|
- **backend-api-validation** (`backend/api/validation.md`) — O validator checa só FORMA (sem DbContext); existência, unicidade e estado ficam no handler com `{Entity}Errors` tipados. Checagem de estado valida o INVÁLIDO (enum ordenado, estados de erro em 100+). PROIBIDO: lançar exceção, validar na lambda do endpoint, misturar DataAnnotations, `if (status != Expected)`, switch exaustivo sobre estados válidos.
|
|
18
19
|
- **architecture-code-comments** (`architecture/code-comments.md`) — Padrão é nenhum comentário: renomeie, extraia, tipe ou teste antes. Só o porquê que o código não mostra (invariante, ordem, quirk externo com link), até ~3 linhas. PROIBIDO: história de processo (task, UC, ADR, Gate), repetir o código, XML doc/JSDoc que repete a assinatura, banner, `#region`, código comentado, TODO solto. O hook `comment-guard` bloqueia.
|
|
20
|
+
- **architecture-engineering-lens** (`architecture/engineering-lens.md`) — Escreva para quem herda amanhã: convenção do projeto, o mais simples, causa raiz. PROIBIDO no que você adiciona: `catch` que engole, flag booleana que bifurca o método (são dois métodos), efeito colateral escondido, símbolo/flag/config sem consumidor, abstração ou camada sem segundo uso, literal repetido sem constante. Nomes de domínio buscáveis, funções pequenas, ≤ 3 parâmetros, condicional positiva. Commit em Conventional Commits (hook `conventional-commit`).
|
|
19
21
|
|
|
20
22
|
> Resumo injetado quando você lê ou edita um arquivo casado por `paths:`. Texto completo: `morph-spec standards --find "<id>"` e leia só a seção necessária de `.morph/framework/standards/<path>`.
|
|
@@ -40,8 +40,8 @@ For each new feature slice, follow the patterns in these templates:
|
|
|
40
40
|
|----------|------|---------|
|
|
41
41
|
| Endpoint | `.morph/framework/templates/code/dotnet/feature/endpoint.cs.hbs` | IApiEndpoint with MapEndpoint() |
|
|
42
42
|
| Handler | `.morph/framework/templates/code/dotnet/feature/handler.cs.hbs` | IHandler<TRequest, TResponse> |
|
|
43
|
-
| Request | `.morph/framework/templates/code/dotnet/feature/request.cs.hbs` |
|
|
44
|
-
| Response | `.morph/framework/templates/code/dotnet/feature/response.cs.hbs` |
|
|
43
|
+
| Request | `.morph/framework/templates/code/dotnet/feature/request.cs.hbs` | record DTO |
|
|
44
|
+
| Response | `.morph/framework/templates/code/dotnet/feature/response.cs.hbs` | record DTO |
|
|
45
45
|
| Validator | `.morph/framework/templates/code/dotnet/feature/validator.cs.hbs` | FluentValidation AbstractValidator |
|
|
46
46
|
| Entity | `.morph/framework/templates/code/dotnet/feature/entity.cs.hbs` | Domain entity |
|
|
47
47
|
| Errors | `.morph/framework/templates/code/dotnet/feature/errors.cs.hbs` | Error factory |
|
|
@@ -19,5 +19,6 @@ stacks:
|
|
|
19
19
|
- **frontend-nextjs-nextjs-patterns** (`frontend/nextjs/nextjs-patterns.md`) — Route Handler é BFF: `auth.getSession()` (401 sem sessão) e delega ao .NET; banco NUNCA em route handler. Zod no client e no server (`safeParse`, 400). `strict: true` + `noUncheckedIndexedAccess`; sem `any` em resposta de API. shadcn via `npx shadcn@latest add`.
|
|
20
20
|
- **frontend-nextjs-motion-patterns** (`frontend/nextjs/motion-patterns.md`) — Motion via `motion/react` (nunca `framer-motion` junto). Variants em `motion/variants.ts` com durations/easings de `@/lib/design-tokens`. `useReducedMotion()` obrigatório; o `layout.tsx` raiz segue Server Component. 3–5 momentos de motion por página; cena guiada por scroll é do GSAP, nunca no mesmo elemento.
|
|
21
21
|
- **architecture-code-comments** (`architecture/code-comments.md`) — Padrão é nenhum comentário: renomeie, extraia, tipe ou teste antes. Só o porquê que o código não mostra (invariante, ordem, quirk externo com link), até ~3 linhas. PROIBIDO: história de processo (task, UC, ADR, Gate), repetir o código, XML doc/JSDoc que repete a assinatura, banner, `#region`, código comentado, TODO solto. O hook `comment-guard` bloqueia.
|
|
22
|
+
- **architecture-engineering-lens** (`architecture/engineering-lens.md`) — Escreva para quem herda amanhã: convenção do projeto, o mais simples, causa raiz. PROIBIDO no que você adiciona: `catch` que engole, flag booleana que bifurca o método (são dois métodos), efeito colateral escondido, símbolo/flag/config sem consumidor, abstração ou camada sem segundo uso, literal repetido sem constante. Nomes de domínio buscáveis, funções pequenas, ≤ 3 parâmetros, condicional positiva. Commit em Conventional Commits (hook `conventional-commit`).
|
|
22
23
|
|
|
23
24
|
> Resumo injetado quando você lê ou edita um arquivo casado por `paths:`. Texto completo: `morph-spec standards --find "<id>"` e leia só a seção necessária de `.morph/framework/standards/<path>`.
|
|
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando for iniciar um novo projeto de LP com o template Umbraco genérico
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- umbraco
|
|
10
10
|
---
|
|
@@ -4,7 +4,7 @@ description: Migra uma landing page existente (React/Vite, Next.js estático ou
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando o usuário pedir "use /migrate-lp-to-umbraco {projeto}" ou descrever a necessidade de migrar uma LP para Umbraco
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- umbraco
|
|
10
10
|
---
|
|
@@ -4,7 +4,7 @@ description: Executa as fatias do plano de uma feature morph-spec — dispatch p
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use com o gate do plano aprovado, em "use /morph-apply {feature}" ou "implementar feature"
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-apply
|
|
@@ -4,7 +4,7 @@ description: ADW cirúrgico de hotfix — resolve uma queda/bug crítico de prod
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando chegar "/morph-hotfix {feature}", quando a morph-plan rotear o pedido como workType hotfix, ou quando o usuário disser "produção caiu / hotfix / urgente" a respeito de um incidente
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-hotfix
|
|
@@ -4,7 +4,7 @@ description: Cuida da RAM e da CPU da máquina enquanto o morph-spec trabalha
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando a máquina estiver lenta ou sem RAM, um comando pesado esperou ou foi recusado pela fila, sobrou worktree de sub-agent, ou vários sub-agents pesados vão rodar em paralelo
|
|
6
6
|
user-invocable: false
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-machine
|
|
@@ -4,7 +4,7 @@ description: Conduz a AUTORIA de mídia gerada por IA — escolher o modelo pelo
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando a tarefa for PRODUZIR imagem ou vídeo por IA (catálogo, marketing, asset de LP) e alguém precisar decidir modelo, prompt e aprovação
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- dotnet
|
|
10
10
|
- nextjs
|
|
@@ -4,7 +4,7 @@ description: Leva uma feature do pedido ao plano aprovado — roteia o tipo de t
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando uma feature, bug ou chore começar ("/morph-plan {feature}", "vamos planejar X"), ou ao retomar uma feature cujo gate plan ainda não foi aprovado
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -95,11 +95,11 @@ O `create` já escreveu o esqueleto em `2-plan/plan.md` (leia e preencha; `chore
|
|
|
95
95
|
|
|
96
96
|
**Arquitetura**: componentes, fluxo de dados, edge cases **técnicos** e integrações; não repita os cenários.
|
|
97
97
|
|
|
98
|
-
**Reuso**: tabela `| conceito | veredito | alvo | why |`, veredito `REUSAR` | `ESTENDER` | `CRIAR`. **`CRIAR` exige `why`** — a consulta que respondeu "não existe" (`graph explain X → not-found; grep -rn X → 0`). Conceito de duas features vai para um módulo comum, nunca import cruzado entre slices. O validador `reuse` do `verify` cobra: símbolo novo homônimo de um existente reprova na mesma feature e avisa entre features; `CRIAR` declarado rebaixa a aviso. Conceito sem fatia que o escreva: falta fatia ou o conceito não é desta feature — resolva antes do gate.
|
|
98
|
+
**Reuso**: tabela `| conceito | veredito | alvo | why |`, veredito `REUSAR` | `ESTENDER` | `CRIAR`. **`CRIAR` exige `why`** — a consulta que respondeu "não existe" (`graph explain X → not-found; grep -rn X → 0`). Abstração nova (interface, base, factory, camada) soma ao `why` o segundo uso ou a próxima adição que ela barateia; sem isso é excesso (`architecture-engineering-lens#excess`). Conceito de duas features vai para um módulo comum, nunca import cruzado entre slices. O validador `reuse` do `verify` cobra: símbolo novo homônimo de um existente reprova na mesma feature e avisa entre features; `CRIAR` declarado rebaixa a aviso. Conceito sem fatia que o escreva: falta fatia ou o conceito não é desta feature — resolva antes do gate.
|
|
99
99
|
|
|
100
100
|
**Fatias** (`### Fn — título`): a unidade de entrega e de relatório (`3-implement/reports/Fn.md`).
|
|
101
101
|
- `persona:` obrigatório — o id sem `morph-`, cujo `domains` cobre a fatia (uma leitura de `agents.json`). Fatia que cria ou altera UI é `ui-designer`, mesmo sem o ramo de design; com `superfície:`, os standards são os daquela chave de `surfaces`.
|
|
102
|
-
- `standards:` — `morph-spec standards --find "<domínio>"` por domínio (o digest decide): os da persona mais o que a fatia exige, sempre `architecture-code-comments` e o de teste do stack; agente MAF soma `--category ai-agents`.
|
|
102
|
+
- `standards:` — `morph-spec standards --find "<domínio>"` por domínio (o digest decide): os da persona mais o que a fatia exige, sempre `architecture-code-comments`, `architecture-engineering-lens` e o de teste do stack; agente MAF soma `--category ai-agents`.
|
|
103
103
|
- `arquivos:` — os paths planejados, testes incluídos, **no máximo 8**: acima disso o sub-agente bate no teto de turnos.
|
|
104
104
|
- `depende:` opcional; `cobre:` lista os Cn — todo cenário é coberto por alguma fatia. Fatias sem `depende:` comum e sem arquivo em comum rodam em paralelo; o `plan-check` imprime as ondas.
|
|
105
105
|
- Cenário que se observa por um artefato de outra fatia (rota, endpoint, componente que ela cria) faz da fatia que o cobre um `depende:` daquela — senão as duas caem na mesma onda e o teste do cenário não tem por onde provar. O `plan-check` não vê de quem é a rota: a regra é sua.
|
|
@@ -4,7 +4,7 @@ description: Conduz o Gate 3 — despacha verificador ∥ revisor (∥ seguranç
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando a fase review começar, com todas as fatias com relatório final e o verify da feature verde
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -4,7 +4,7 @@ description: Localiza um standard específico via busca server-side (morph-spec
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando precisar consultar um standard específico durante a implementação
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|
|
@@ -4,7 +4,7 @@ description: Conduz a fase opcional de UI/UX de uma feature com plano aprovado
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando o ramo de design for tomado (morph-spec advance {feature} --design, depois do gate do plano) e antes de /morph-apply
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.4.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-uiux
|
|
@@ -496,7 +496,7 @@
|
|
|
496
496
|
"example": "#exemplo-c-completo",
|
|
497
497
|
"content-types": "#datacontent-vs-uricontent"
|
|
498
498
|
},
|
|
499
|
-
"digest": "Vision em MAF usa ChatMessage multimodal (Microsoft.Extensions.AI): new ChatMessage(ChatRole.User, [new TextContent(pergunta), imagem]) passado a agent.RunAsync. Imagem em URL pública: UriContent(uri, mediaType); arquivo local: DataContent.LoadFromAsync(path), o helper canônico e preferido; bytes já em memória: new DataContent(BinaryData, mediaType). Upload de usuário ou imagem privada usa DataContent, nunca URL temporária (pode expirar); CDN estável usa UriContent. O modelo vem do alias vision no Model Registry, pois nem todo provider suporta visão. Dado tipado: RunAsync<T> com
|
|
499
|
+
"digest": "Vision em MAF usa ChatMessage multimodal (Microsoft.Extensions.AI): new ChatMessage(ChatRole.User, [new TextContent(pergunta), imagem]) passado a agent.RunAsync. Imagem em URL pública: UriContent(uri, mediaType); arquivo local: DataContent.LoadFromAsync(path), o helper canônico e preferido; bytes já em memória: new DataContent(BinaryData, mediaType). Upload de usuário ou imagem privada usa DataContent, nunca URL temporária (pode expirar); CDN estável usa UriContent. O modelo vem do alias vision no Model Registry, pois nem todo provider suporta visão. Dado tipado: RunAsync<T> com record, nunca regex em response.Text. Proibido: base64 manual, imagem 4K sem resize (tokens cobrados por resolução; ex.: 800px para OCR), vision quando já há texto extraído ou um parser/OCR determinístico basta. PDF: confirme application/pdf no provider."
|
|
500
500
|
},
|
|
501
501
|
{
|
|
502
502
|
"id": "ai-agents-multi-agent-patterns",
|
|
@@ -692,7 +692,7 @@
|
|
|
692
692
|
"type": "#definindo-o-tipo-de-saída",
|
|
693
693
|
"validation": "#validação-pós-call"
|
|
694
694
|
},
|
|
695
|
-
"digest": "Every agent whose output feeds another system returns a typed record, never free text. Form 1 is RunAsync<T>; Form 2 is ResponseFormat = ChatResponseFormat.ForJsonSchema<T>() in ChatOptions. Type is a
|
|
695
|
+
"digest": "Every agent whose output feeds another system returns a typed record, never free text. Form 1 is RunAsync<T>; Form 2 is ResponseFormat = ChatResponseFormat.ForJsonSchema<T>() in ChatOptions. Type is a record with [Description] on the type and [JsonPropertyName] on every property. Documented limits: ResponseFormat does not accept a primitive or an array at the root (use a wrapper), and in streaming you must build the whole response with ToAgentResponseAsync() before deserialising. Schema validates shape, not semantics — always validate business rules after the call. Do not use temperature as the example of a normal ChatOption."
|
|
696
696
|
},
|
|
697
697
|
{
|
|
698
698
|
"id": "ai-agents-sweet-spot",
|
|
@@ -801,7 +801,7 @@
|
|
|
801
801
|
"rules": "#padrões-obrigatórios",
|
|
802
802
|
"cqrs": "#anti-patterns-nunca-fazer-em-vsa"
|
|
803
803
|
},
|
|
804
|
-
"digest": "Uma slice por operação em Features/{Entity}Feature/{Verb}{Entity}/: Request e Response (
|
|
804
|
+
"digest": "Uma slice por operação em Features/{Entity}Feature/{Verb}{Entity}/: Request e Response (records) no mesmo arquivo do Handler, Validator FluentValidation (nenhum em GetAll) e Endpoint `IApiEndpoint` `internal`. O handler implementa o `IHandler<TRequest, TResponse>` próprio, devolve `Result<T>` (erros em `{Entity}Errors`, 1 por feature), injeta `ApplicationDbContext` direto e chama `SaveChangesAsync` nas escritas; o endpoint usa `result.Match()`: o sucesso recebe o valor no lambda (nunca `result.Value`) e a falha vira `error.ToHttpResult()` (status pelo `ErrorType`, ProblemDetails), nunca `Results.BadRequest` para todo erro, declarando cada status com `.ProducesProblem`. Decorators Scrutor: Logging -> Validation -> Handler. PROIBIDO: MediatR/IRequest, AggregateRoot, DomainEvent, CQRS formal, Application Service layer, DTOs compartilhados entre features e `IRepository<T>`/`IUnitOfWork` (DbSet já é repository, vira código morto); store dedicado só com justificativa em `plan.md` (`## Decisões`). Não misture VSA com DDD."
|
|
805
805
|
},
|
|
806
806
|
{
|
|
807
807
|
"id": "backend-api-validation",
|
|
@@ -821,7 +821,7 @@
|
|
|
821
821
|
"rules": "#validation-patterns",
|
|
822
822
|
"state": "#state-checks-in-the-handler"
|
|
823
823
|
},
|
|
824
|
-
"digest": "Request/DTO validation in the VSA slice: one
|
|
824
|
+
"digest": "Request/DTO validation in the VSA slice: one `AbstractValidator<TRequest>` (FluentValidation) next to the handler, checking SHAPE only (required, length, range, format, child items via RuleForEach/ChildRules; no DbContext; time rules read an injected TimeProvider inside the rule). The ValidationDecorator runs it before the handler and short-circuits with `Result.Failure(new ValidationError([...]))`; the endpoint maps it with `error.ToHttpResult()` to 400 ProblemDetails with `errors[]` (decorator and conversion in fluent-validation-vsa.md, ValidationError/ErrorHttpMapping in result-pattern.md). Never throw (ValidationException/InvalidOperationException), never validate in the endpoint lambda, never mix DataAnnotations with FluentValidation (minimal APIs have no ModelState). Existence/uniqueness/state checks live in the handler and return typed `{Entity}Errors` (NotFound 404, Conflict 409, Failure 400), never `Result.Failure(\"string\")`. State checks validate what is INVALID, not what is valid, so a new flow (e.g. a free flow that skips PendingPayment) does not break: ordered status enum (Created=0 ... EmailSent=4) with error states at 100+ (Failed=100, Cancelled=101, Refunded=102), blocked by `status >= Completed`, an error-state list, or `status >= Failed`. FORBIDDEN: `if (status != Expected)`, exhaustive switches over valid states, business rules mixed into the state check (keep `RequiresPayment && !IsPaid` separate). Document all flows in plan.md (## Decisões) and test each."
|
|
825
825
|
},
|
|
826
826
|
{
|
|
827
827
|
"id": "backend-database-postgresql-database",
|
|
@@ -883,7 +883,7 @@
|
|
|
883
883
|
"decorator": "#validationdecorator-pattern",
|
|
884
884
|
"conversion": "#fluentvalidation-to-validationerror-conversion"
|
|
885
885
|
},
|
|
886
|
-
"digest": "One
|
|
886
|
+
"digest": "One `AbstractValidator<TRequest>` per request DTO (FluentValidation 12), in the same feature folder as the handler; no `RuleSet` grouping and no `IValidateOptions<T>`. Never validate request shape inside the handler or endpoint (existence/state checks do belong in the handler): `ValidationDecorator<TRequest, TResponse>` wraps every `IHandler` via Scrutor, collects all `IValidator<TRequest>`, runs them in parallel (`Task.WhenAll`) and on failure short-circuits to `Result.Failure(ValidationError)` (an array of `Error.Validation(code, message)`), supporting both `Result` and `Result<T>`. Never throw `ValidationException`. In Program.cs call `AddValidatorsFromAssembly()` BEFORE `AddHandlersFromAssembly()` so the decorator can resolve validators. Always give user-facing `.WithMessage()` text. Inject `TimeProvider` instead of reading `DateTime.UtcNow` and read it INSIDE the rule (`LessThanOrEqualTo(_ => timeProvider.GetUtcNow().Year)`), so the year is evaluated per validation (a value captured in the constructor goes stale in a singleton/cached validator; AddValidatorsFromAssembly defaults to Scoped) and stays testable with `FakeTimeProvider`."
|
|
887
887
|
},
|
|
888
888
|
{
|
|
889
889
|
"id": "backend-dotnet-result-pattern",
|
|
@@ -988,7 +988,7 @@
|
|
|
988
988
|
"endpoint": "#complete-endpoint-example",
|
|
989
989
|
"registration": "#programcs-di-registration-order"
|
|
990
990
|
},
|
|
991
|
-
"digest": "Each VSA feature is a slice in `Features/{Feature}/{UseCase}/` with 5 files: Request, Response (
|
|
991
|
+
"digest": "Each VSA feature is a slice in `Features/{Feature}/{UseCase}/` with 5 files: Request, Response (records, may be inline), Handler, Validator, Endpoint; feature errors in `{Feature}Errors.cs` (`Error.NotFound/Conflict/Failure`). Handlers implement `IHandler<TRequest, TResponse>` (resolved from DI; NOT MediatR, decorators via Scrutor), inject `ApplicationDbContext` directly (`DbSet<T>` is the repository, `DbContext` the unit of work: no service layer, no `IRepository<T>`/`IUnitOfWork`), return `Result<T>` instead of throwing for expected failures. Endpoints are `internal` `IApiEndpoint`, only map HTTP and call `Match()`: success lambda receives the value (never read `.Value`), failure is `error.ToHttpResult()` (status by `ErrorType`, never `Results.BadRequest` for every error), with `.ProducesProblem` per failure status. Raw SQL or a dedicated hand-written store (outbox, lease) only with a `plan.md` (`## Decisões`) justification."
|
|
992
992
|
},
|
|
993
993
|
{
|
|
994
994
|
"id": "backend-integrations-asaas-asaas-api",
|
|
@@ -1028,7 +1028,7 @@
|
|
|
1028
1028
|
"jobs": "#job-types",
|
|
1029
1029
|
"retry": "#retry--error-handling"
|
|
1030
1030
|
},
|
|
1031
|
-
"digest": "Hangfire 1.8.25 on .NET 10, only with a real reason (survive restarts, schedules, deferred retries) recorded in plan.md (## Decisões); work that fits in the request stays in the slice handler. Storage is Neon Postgres via Hangfire.PostgreSql 1.21.1 with `UsePostgreSqlStorage(o => o.UseNpgsqlConnection(cs))` (the string overloads are [Obsolete]) and `HangfireConnection` on the direct/unpooled endpoint (schema install at startup is a migration, which Neon routes to the direct string; EnableLongPolling uses LISTEN/NOTIFY, unsupported by Neon's PgBouncer transaction mode); reference Newtonsoft.Json 13.0.4 explicitly or restore fails with NU1903 (Hangfire.Core pulls 11.0.1). A job is a thin
|
|
1031
|
+
"digest": "Hangfire 1.8.25 on .NET 10, only with a real reason (survive restarts, schedules, deferred retries) recorded in plan.md (## Decisões); work that fits in the request stays in the slice handler. Storage is Neon Postgres via Hangfire.PostgreSql 1.21.1 with `UsePostgreSqlStorage(o => o.UseNpgsqlConnection(cs))` (the string overloads are [Obsolete]) and `HangfireConnection` on the direct/unpooled endpoint (schema install at startup is a migration, which Neon routes to the direct string; EnableLongPolling uses LISTEN/NOTIFY, unsupported by Neon's PgBouncer transaction mode); reference Newtonsoft.Json 13.0.4 explicitly or restore fails with NU1903 (Hangfire.Core pulls 11.0.1). A job is a thin class in the slice that calls the slice handler (`IHandler<TRequest, Result>`); no XService layer. Hangfire.AspNetCore's AspNetCoreJobActivator opens a DI scope per job, so inject the ApplicationDbContext/handler directly (IDbContextFactory only for parallel work inside one job); never capture request-scoped services into Enqueue. Jobs are idempotent (check state, return). Expected failures come back as Result and the job logs and completes; unexpected ones are the exceptions EF/HttpClient already throw, uncaught, which `[AutomaticRetry(Attempts = 3, DelaysInSeconds = [60, 300, 900])]` retries; no try/catch, no manual retry loops. API: `BackgroundJob.Enqueue<TJob>(j => j.RunAsync(id, CancellationToken.None))`, `Schedule`, `RecurringJob.AddOrUpdate` with a stable id and cron (UTC), `ContinueJobWith`. Dashboard: IDashboardAuthorizationFilter requiring authenticated Admin, dev bypass only via IHostEnvironment.IsDevelopment() (never Request.Host, which the client controls)."
|
|
1032
1032
|
},
|
|
1033
1033
|
{
|
|
1034
1034
|
"id": "backend-integrations-neon-auth-neon-auth",
|
|
@@ -1857,7 +1857,7 @@
|
|
|
1857
1857
|
"reais": "#exemplos-reais",
|
|
1858
1858
|
"regras": "#regras"
|
|
1859
1859
|
},
|
|
1860
|
-
"digest": "A tool that carries business rule keeps apart three things that change for different reasons: the RULE (
|
|
1860
|
+
"digest": "A tool that carries business rule keeps apart three things that change for different reasons: the RULE (class XOps(...), explicit AddScoped; never *Service, which MORPH.md bans as a layer between handler and data), the sentence the MODEL reads, and the EFFECT a human sees. Each Ops method returns a typed outcome (abstract record XDesfecho, one record per case); the model-facing text (description, refusal, self-correction, success, the ToolUnavailable decision) lives in the adapter, in public static object Render(XDesfecho), while human text delivered by code (task body, note, ack) stays in Ops because it is an effect. Checkable by grep: Ops names no tool and instructs no model. The adapter (AIFunctionFactory.Create) only builds the typed request, calls Ops and returns Render: zero rule, not a line count, and a long dynamic description stays byte for byte. Two test layers: Ops tests assert outcome, calls and order on the fake, never text; contract tests assert Render's exact literal and .Description, with a golden of name, description and schema per real tenant. A neutral refactor is proven before any test moves: a proof commit with the old suite untouched and green (offline evals included), then the migration, with mutation run only against the new tests. \"Already covered\" needs proof assertion by assertion (11 of 22 such claims had lost one); the tests left at the origin must be identical to the base; mutation on the new tests found rules the old suite never protected. The C# blocks were compiled against the pin and the three xunit tests run."
|
|
1861
1861
|
},
|
|
1862
1862
|
{
|
|
1863
1863
|
"id": "frontend-design-system-art-direction",
|
|
@@ -1879,6 +1879,53 @@
|
|
|
1879
1879
|
"prototype": "#section-7-prototype-and-self-review"
|
|
1880
1880
|
},
|
|
1881
1881
|
"digest": "Order beats talent: before any component of a new or changed screen, write 1-design/direcao.md with the concept in one sentence, the visual metaphor, what makes it memorable, and what it must NOT look like. New screens and brand moments (login, onboarding, first screen, empty state, success) explore 2-3 DISTINCT directions and recommend one with a product reason. Avoid template tells (raw shadcn look, white card centered on gray, generic icon list, default fonts, flat backgrounds, mobile as stacked desktop, decorative fade-ins, made-up numbers); three or more means raise it. Mobile is composed at 390px on its own (bottom sheet, thumb zone, 44px targets). Motion tells something with the lightest tool (CSS keyframes > scroll-driven CSS > Framer Motion > GSAP), transform/opacity only, and a composed reduced-motion version. Prove it in 1-design/prototipos/{screen}.html with the project REAL tokens, screenshotted at 1280 and 390 and revised until it passes."
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
"id": "architecture-engineering-lens",
|
|
1885
|
+
"name": "engineering lens",
|
|
1886
|
+
"path": "architecture/engineering-lens.md",
|
|
1887
|
+
"category": "architecture",
|
|
1888
|
+
"subcategory": null,
|
|
1889
|
+
"tags": [
|
|
1890
|
+
"architecture",
|
|
1891
|
+
"clean-code",
|
|
1892
|
+
"review"
|
|
1893
|
+
],
|
|
1894
|
+
"stacks": [
|
|
1895
|
+
"*"
|
|
1896
|
+
],
|
|
1897
|
+
"anchors": {
|
|
1898
|
+
"fragility": "#fragilidade",
|
|
1899
|
+
"dead-code": "#código-morto",
|
|
1900
|
+
"excess": "#excesso",
|
|
1901
|
+
"readability": "#legibilidade",
|
|
1902
|
+
"extensibility": "#extensibilidade",
|
|
1903
|
+
"ai-vs-code": "#ia-e-código",
|
|
1904
|
+
"git": "#git",
|
|
1905
|
+
"review-blockers": "#o-que-bloqueia-no-gate-3"
|
|
1906
|
+
},
|
|
1907
|
+
"digest": "Write for whoever inherits the repo tomorrow: the project convention beats taste, the simplest thing that works beats the general one, fix the root cause. Fragility: no swallowed catch, handle null/absence from boundaries (HTTP, DB, file, LLM), no hidden order dependency, no strict parser on real data, no blind guard, no test that passes without proving the rule. Dead code: a symbol, endpoint, flag, config or table without a real consumer goes, and so does commented-out code; two paths doing the same thing become one. Excess: no abstraction without a second use today, no pass-through layer, no speculative config, no reinvented stdlib; a plan CREATE of an abstraction names the second use or the next addition it makes cheaper. Readability: descriptive searchable domain names, nouns for classes and verbs for methods, small single-purpose functions, at most 3 parameters, no boolean flag that forks the method (two methods), no hidden side effect, positive conditionals, repeated literals become constants. Extensibility: the next likely addition touches few files and no scattered switch. AI: an unbreakable rule stays in code (ai-agents-guardrails); hand-written NL classification may become structured output only when measured by eval. Git: branch morph/{f}; Conventional Commits <type>(<scope>): <subject>, enforced by the conventional-commit hook. Gate 3 blocks only what the DIFF introduced: swallowed exception, boolean flag parameter, hidden side effect, symbol/flag/config without consumer or commented-out code, abstraction without second use nor plan why, repeated magic literal in a comparison; everything else is a note."
|
|
1908
|
+
},
|
|
1909
|
+
{
|
|
1910
|
+
"id": "backend-dotnet-analyzers",
|
|
1911
|
+
"name": "analyzers",
|
|
1912
|
+
"path": "backend/dotnet/analyzers.md",
|
|
1913
|
+
"category": "backend",
|
|
1914
|
+
"subcategory": "dotnet",
|
|
1915
|
+
"tags": [
|
|
1916
|
+
"backend",
|
|
1917
|
+
"dotnet",
|
|
1918
|
+
"analyzers"
|
|
1919
|
+
],
|
|
1920
|
+
"stacks": [
|
|
1921
|
+
"dotnet"
|
|
1922
|
+
],
|
|
1923
|
+
"anchors": {
|
|
1924
|
+
"files": "#arquivos",
|
|
1925
|
+
"rules": "#o-que-cada-regra-compra",
|
|
1926
|
+
"not-a-rule": "#o-que-não-é-regra"
|
|
1927
|
+
},
|
|
1928
|
+
"digest": "Rules with a reason that must never break are build errors, not prose: three files at the solution root, no .csproj change. Directory.Build.props adds Microsoft.CodeAnalysis.BannedApiAnalyzers 5.6.0 (PrivateAssets=all), BannedSymbols.txt as AdditionalFiles and morph.globalconfig as GlobalAnalyzerConfigFiles (with Central Package Management the version goes to Directory.Packages.props). BannedSymbols.txt bans M:System.Guid.NewGuid (use Guid.CreateVersion7(): time-ordered, does not fragment the Postgres PK index). morph.globalconfig sets RS0030 and CA2016 (a CancellationToken received and not forwarded) to error. A new banned API is one BannedSymbols.txt line with the reason after the semicolon, never a prose rule. sealed, primary constructors, var, expression bodies and using order are taste: neither prose nor analyzer; follow the neighbouring file."
|
|
1882
1929
|
}
|
|
1883
1930
|
]
|
|
1884
1931
|
}
|
|
@@ -1223,6 +1223,34 @@
|
|
|
1223
1223
|
],
|
|
1224
1224
|
"verifiedAgainst": "the `login-ciclo-do-produto` design round (2026-10-06): a correct, plain design was rejected; a second round with a written direction, an HTML prototype and a screenshot self-review was approved on first sight.",
|
|
1225
1225
|
"provedBy": []
|
|
1226
|
+
},
|
|
1227
|
+
"architecture-engineering-lens": {
|
|
1228
|
+
"aliases": [
|
|
1229
|
+
"clean code",
|
|
1230
|
+
"código limpo",
|
|
1231
|
+
"código morto",
|
|
1232
|
+
"dead code",
|
|
1233
|
+
"fragilidade",
|
|
1234
|
+
"excesso",
|
|
1235
|
+
"extensibilidade",
|
|
1236
|
+
"flag argument",
|
|
1237
|
+
"conventional commits"
|
|
1238
|
+
],
|
|
1239
|
+
"verifiedAgainst": "C#, TypeScript e JavaScript (convenção de linguagem, sem pacote externo); Conventional Commits 1.0.0.",
|
|
1240
|
+
"provedBy": []
|
|
1241
|
+
},
|
|
1242
|
+
"backend-dotnet-analyzers": {
|
|
1243
|
+
"aliases": [
|
|
1244
|
+
"analyzer",
|
|
1245
|
+
"roslyn analyzer",
|
|
1246
|
+
"banned api",
|
|
1247
|
+
"BannedSymbols",
|
|
1248
|
+
"Guid.NewGuid",
|
|
1249
|
+
"CreateVersion7",
|
|
1250
|
+
"CA2016"
|
|
1251
|
+
],
|
|
1252
|
+
"verifiedAgainst": ".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).",
|
|
1253
|
+
"provedBy": []
|
|
1226
1254
|
}
|
|
1227
1255
|
},
|
|
1228
1256
|
"sharedAliases": {
|