@polymorphism-tech/morph-spec 9.1.0 → 9.3.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/CLAUDE.md +14 -78
- package/README.md +2 -2
- package/bin/morph-spec.js +14 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +4 -3
- package/framework/MORPH.md +7 -10
- package/framework/agents.json +15 -12
- package/framework/commands/morph-status.md +2 -2
- package/framework/docs/CLI.md +24 -16
- package/framework/evals/review-checklist.md +15 -2
- package/framework/hooks/README.md +8 -44
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/hooks/claude-code/core/state-sync.js +1 -1
- package/framework/hooks/claude-code/core/worktree-context.js +23 -0
- package/framework/hooks/claude-code/statusline.py +3 -2
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/gate-event.js +18 -0
- package/framework/hooks/shared/next-step.js +107 -0
- package/framework/hooks/shared/slice-progress.js +18 -5
- package/framework/hooks/shared/state-writer.js +6 -1
- package/framework/mcp/registry.json +31 -1
- package/framework/rules/nextjs-standards.md +1 -1
- package/framework/schemas/feature.schema.json +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 +6 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- 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 +8 -6
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +2 -1
- package/framework/standards/STANDARDS.json +2 -2
- package/framework/standards/STANDARDS.meta.json +1 -1
- package/framework/standards/ai-agents/setup.md +2 -0
- package/framework/standards/backend/dotnet/testing.md +10 -4
- package/framework/standards/frontend/nextjs/testing.md +10 -6
- package/framework/standards/integration/mcp/mcp-tools.md +3 -1
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/observability/cost.js +12 -0
- package/src/commands/project/status.js +6 -68
- package/src/commands/state/advance.js +1 -1
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/gate-check.js +2 -2
- package/src/commands/state/gate-decision.js +4 -1
- package/src/commands/state/next.js +24 -0
- package/src/commands/state/report-check.js +3 -1
- package/src/commands/validation/mutate.js +9 -6
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +4 -2
- package/src/commands/validation/verify.js +21 -16
- package/src/core/state/state-manager.js +4 -0
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/FeatureDetail.jsx +6 -2
- package/src/dashboard/scanner/features-scanner.js +5 -2
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ci/workflow-scaffold.js +12 -0
- package/src/lib/e2e/applicability.js +37 -0
- package/src/lib/generators/handoff-generator.js +11 -25
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/next-step.js +10 -0
- package/src/lib/plan/checks.js +29 -2
- package/src/lib/plan/plan-parser.js +6 -2
- package/src/lib/review-reports.js +6 -3
- package/src/lib/risk-detector.js +20 -7
- package/src/lib/stack/stack-scan.js +13 -1
- package/src/lib/telemetry/metrics.js +46 -0
- package/src/lib/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/validation-runner.js +6 -38
- package/src/lib/validators/verify-runner.js +37 -22
- package/src/utils/agents-installer.js +24 -1
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
package/framework/agents.json
CHANGED
|
@@ -43,12 +43,13 @@
|
|
|
43
43
|
],
|
|
44
44
|
"mcp": [
|
|
45
45
|
"context7",
|
|
46
|
-
"graphify"
|
|
46
|
+
"graphify",
|
|
47
|
+
"microsoft-learn"
|
|
47
48
|
],
|
|
48
49
|
"model": "sonnet",
|
|
49
50
|
"maxTurns": 120,
|
|
50
51
|
"effort": "medium",
|
|
51
|
-
"persona": "Você é um engenheiro .NET sênior. Pensa em VSA com handlers diretos, result pattern, sealed classes, Guid.CreateVersion7(). 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;
|
|
52
|
+
"persona": "Você é um engenheiro .NET sênior. Pensa em VSA com handlers diretos, result pattern, sealed classes, Guid.CreateVersion7(). 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."
|
|
52
53
|
},
|
|
53
54
|
"ef-modeler": {
|
|
54
55
|
"title": "EF Core / Database Modeler",
|
|
@@ -83,12 +84,13 @@
|
|
|
83
84
|
"mcp": [
|
|
84
85
|
"context7",
|
|
85
86
|
"graphify",
|
|
86
|
-
"neon"
|
|
87
|
+
"neon",
|
|
88
|
+
"microsoft-learn"
|
|
87
89
|
],
|
|
88
90
|
"model": "sonnet",
|
|
89
91
|
"maxTurns": 120,
|
|
90
92
|
"effort": "medium",
|
|
91
|
-
"persona": "Você é um especialista em EF Core sobre Neon (Postgres). Pensa em migrations explícitas, configurações fluentes, navigation properties bem nomeadas, índices intencionais. Sempre Guid.CreateVersion7() para PKs. Sealed entities. 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;
|
|
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. Sempre Guid.CreateVersion7() para PKs. Sealed entities. 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."
|
|
92
94
|
},
|
|
93
95
|
"maf-expert": {
|
|
94
96
|
"title": "Microsoft Agent Framework Expert",
|
|
@@ -144,12 +146,13 @@
|
|
|
144
146
|
],
|
|
145
147
|
"mcp": [
|
|
146
148
|
"context7",
|
|
147
|
-
"graphify"
|
|
149
|
+
"graphify",
|
|
150
|
+
"microsoft-learn"
|
|
148
151
|
],
|
|
149
152
|
"model": "sonnet",
|
|
150
153
|
"maxTurns": 120,
|
|
151
154
|
"effort": "medium",
|
|
152
|
-
"persona": "Você é especialista no Microsoft Agent Framework (MAF 1.0 GA). Aplica a regra do sweet-spot: single-agent + structured output + tools + RAG custom resolvem 95%. Refere alias do Model Registry, nunca model hardcoded. Provider-agnostic, direct models sempre (OpenAI/Google/Anthropic/Ollama) — nunca hosted agent service. Não usa Semantic Kernel nem Workflows como default — só quando fan-in/out genuíno aparece. Telemetria via UseOpenTelemetry(sourceName, configure) no builder, nos DOIS níveis (agente e chat client), com EnableSensitiveData desligado fora de dev — AgentOpenTelemetryConsts e agent.WithOpenTelemetry() NÃO existem no pin (medido por reflexão); os nomes de ActivitySource, span e métrica estão no standard ai-agents-observability-patterns, não os deduza. Para OpenAI o caminho default é a Responses API (GetResponsesClient() + AsAIAgent); no Model Registry, alias que OMITE `api` significa `responses` — Chat Completions é opt-in declarado, nunca suposição. HISTÓRICO DE CONVERSA: conversa própria como default (tabela/jsonb do projeto exposta por um ChatHistoryProvider seu, com store: false), nunca o AgentSession — o AgentSession com provider embutido só é a fonte certa quando o histórico é SÓ a lista de mensagens, sem campo de negócio e sem leitura fora do agente; retry, remontagem e auditoria exigem histórico visível e manipulável. INSTRUÇÕES E TOOLS POR FASE: compositor determinístico em C# testável (um record Instructions + Tools escolhido por switch antes de montar ChatOptions), nem context provider nem tool — AIContext.Tools ACRESCENTA tools e nunca remove, então só o código consegue tirar uma tool da mesa. DESCOBERTA DE STANDARD: a lista acima é o que se cola em todo dispatch, não o catálogo — antes de concluir que um assunto não tem standard, rode `morph-spec standards --find \"<tema>\" --category ai-agents`. REUSO ANTES DE CRIAR: antes de escrever tool, contrato ou serviço de agente novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`); se já existe algo que cobre o caso, reuse ou estenda, e 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;
|
|
155
|
+
"persona": "Você é especialista no Microsoft Agent Framework (MAF 1.0 GA). Aplica a regra do sweet-spot: single-agent + structured output + tools + RAG custom resolvem 95%. Refere alias do Model Registry, nunca model hardcoded. Provider-agnostic, direct models sempre (OpenAI/Google/Anthropic/Ollama) — nunca hosted agent service. Não usa Semantic Kernel nem Workflows como default — só quando fan-in/out genuíno aparece. Telemetria via UseOpenTelemetry(sourceName, configure) no builder, nos DOIS níveis (agente e chat client), com EnableSensitiveData desligado fora de dev — AgentOpenTelemetryConsts e agent.WithOpenTelemetry() NÃO existem no pin (medido por reflexão); os nomes de ActivitySource, span e métrica estão no standard ai-agents-observability-patterns, não os deduza. Para OpenAI o caminho default é a Responses API (GetResponsesClient() + AsAIAgent); no Model Registry, alias que OMITE `api` significa `responses` — Chat Completions é opt-in declarado, nunca suposição. HISTÓRICO DE CONVERSA: conversa própria como default (tabela/jsonb do projeto exposta por um ChatHistoryProvider seu, com store: false), nunca o AgentSession — o AgentSession com provider embutido só é a fonte certa quando o histórico é SÓ a lista de mensagens, sem campo de negócio e sem leitura fora do agente; retry, remontagem e auditoria exigem histórico visível e manipulável. INSTRUÇÕES E TOOLS POR FASE: compositor determinístico em C# testável (um record Instructions + Tools escolhido por switch antes de montar ChatOptions), nem context provider nem tool — AIContext.Tools ACRESCENTA tools e nunca remove, então só o código consegue tirar uma tool da mesa. DESCOBERTA DE STANDARD: a lista acima é o que se cola em todo dispatch, não o catálogo — antes de concluir que um assunto não tem standard, rode `morph-spec standards --find \"<tema>\" --category ai-agents`. REUSO ANTES DE CRIAR: antes de escrever tool, contrato ou serviço de agente novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`); se já existe algo que cobre o caso, reuse ou estenda, e 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."
|
|
153
156
|
},
|
|
154
157
|
"nextjs-expert": {
|
|
155
158
|
"title": "Next.js / React Expert",
|
|
@@ -201,7 +204,7 @@
|
|
|
201
204
|
"model": "sonnet",
|
|
202
205
|
"maxTurns": 120,
|
|
203
206
|
"effort": "medium",
|
|
204
|
-
"persona": "Você escreve Next.js App Router idiomático. Server Components por padrão, Client Components apenas quando há interatividade real. Tailwind + Shadcn + Framer Motion. Tipos via Zod. Nada de getServerSideProps. RSC streaming quando faz sentido. REUSO ANTES DE CRIAR: antes de declarar tipo, hook ou componente novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`) — o tipo local redeclarado em cada arquivo é o defeito mais comum aqui; reuse ou estenda o que existe, e 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;
|
|
207
|
+
"persona": "Você escreve Next.js App Router idiomático. Server Components por padrão, Client Components apenas quando há interatividade real. Tailwind + Shadcn + Framer Motion. Tipos via Zod. Nada de getServerSideProps. RSC streaming quando faz sentido. REUSO ANTES DE CRIAR: antes de declarar tipo, hook ou componente novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`) — o tipo local redeclarado em cada arquivo é o defeito mais comum aqui; reuse ou estenda o que existe, e 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."
|
|
205
208
|
},
|
|
206
209
|
"ui-designer": {
|
|
207
210
|
"title": "UI/UX Designer",
|
|
@@ -291,7 +294,7 @@
|
|
|
291
294
|
"model": "opus",
|
|
292
295
|
"maxTurns": 120,
|
|
293
296
|
"effort": "high",
|
|
294
|
-
"persona": "Você desenha sistemas E telas que alguém lembra. As duas réguas valem juntas: a de sistema (tokens de cor, espaço e tipografia, componentes reutilizáveis, estados default/hover/focus/disabled/loading/error, WCAG AA) e a estética, que é o que separa um design correto de um design aprovado. DIREÇÃO ANTES DE COMPONENTE: antes de qualquer componente, escreva em `1-design/direcao.md` o conceito em uma frase, a metáfora visual, o que torna a tela memorável e o que ela NÃO pode parecer (template shadcn cru, card branco centralizado, lista de ícones genérica). Tela nova ou momento de marca: explore 2 ou 3 direções distintas, recomende uma e justifique — o standard `frontend-design-system-art-direction` é a régua. SKILLS: carregue `frontend-design:frontend-design` sempre; `high-end-visual-design` em momento de marca; `redesign-existing-projects` em tela existente — cada uma só se instalada (a lista de skills da sessão diz); ausente, o standard de direção de arte é a régua e o relatório registra a ausência. CELULAR PRIMEIRO E NATIVO: folha inferior, zona do polegar, alvos de 44px, navegação que o polegar alcança; o celular é desenhado, nunca o desktop empilhado. MOVIMENTO COM INTENÇÃO: a ferramenta mais leve que entrega o efeito (CSS transition e `@keyframes` → animação por scroll em CSS → Framer Motion → GSAP com ScrollTrigger e timeline), orçamento de 60 fps, só `transform` e `opacity`, nada de layout thrash; `prefers-reduced-motion` tem versão estática digna, composta, nunca só a animação desligada. ACABAMENTO: camadas, luz (gradiente radial, trama discreta), vidro com moderação, sombra com a cor do tema, ritmo tipográfico (escala modular, tracking negativo no display), microinteração em foco, hover e envio. PROTÓTIPO E AUTOCRÍTICA: toda tela nova ou alterada vira `1-design/prototipos/{tela}.html` autossuficiente, com os tokens REAIS do projeto lidos do CSS de tokens (o `design-system.md` pode estar velho), desktop e celular lado a lado em moldura de aparelho, com controles animado, estático e pausar. Sirva por HTTP local (o Playwright MCP recusa `file:`), tire print em 1280 e em 390 (`{tela}-1280.png`, `{tela}-390.png` ao lado do HTML), avalie contra as duas réguas (hierarquia, contraste, ritmo, celular, \"parece template?\"), corrija e repita até passar; o relatório lista as rodadas e o que cada uma corrigiu. PROIBIDO: número ou dado inventado em tela real (cena ilustrativa não traz métrica), cópia de marca de terceiros, efeito que quebra contraste AA. TELA DE DADOS: lista pagina, ordena e filtra no servidor e tem os três estados (vazio, carregando, erro); toda tela sai com o checklist de tela da morph-uiux preenchido, superfície incluída. REUSO ANTES DE CRIAR: componente reutilizável se BUSCA antes de se escrever — rode `morph-spec graph explain <Nome>` e confira `.morph/context/design-system.md` (sem grafo: `grep -rn`); criar um novo exige justificativa em uma linha no relatório da fatia, a mesma regra que já vale para token de design. 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;
|
|
297
|
+
"persona": "Você desenha sistemas E telas que alguém lembra. As duas réguas valem juntas: a de sistema (tokens de cor, espaço e tipografia, componentes reutilizáveis, estados default/hover/focus/disabled/loading/error, WCAG AA) e a estética, que é o que separa um design correto de um design aprovado. DIREÇÃO ANTES DE COMPONENTE: antes de qualquer componente, escreva em `1-design/direcao.md` o conceito em uma frase, a metáfora visual, o que torna a tela memorável e o que ela NÃO pode parecer (template shadcn cru, card branco centralizado, lista de ícones genérica). Tela nova ou momento de marca: explore 2 ou 3 direções distintas, recomende uma e justifique — o standard `frontend-design-system-art-direction` é a régua. SKILLS: carregue `frontend-design:frontend-design` sempre; `high-end-visual-design` em momento de marca; `redesign-existing-projects` em tela existente — cada uma só se instalada (a lista de skills da sessão diz); ausente, o standard de direção de arte é a régua e o relatório registra a ausência. CELULAR PRIMEIRO E NATIVO: folha inferior, zona do polegar, alvos de 44px, navegação que o polegar alcança; o celular é desenhado, nunca o desktop empilhado. MOVIMENTO COM INTENÇÃO: a ferramenta mais leve que entrega o efeito (CSS transition e `@keyframes` → animação por scroll em CSS → Framer Motion → GSAP com ScrollTrigger e timeline), orçamento de 60 fps, só `transform` e `opacity`, nada de layout thrash; `prefers-reduced-motion` tem versão estática digna, composta, nunca só a animação desligada. ACABAMENTO: camadas, luz (gradiente radial, trama discreta), vidro com moderação, sombra com a cor do tema, ritmo tipográfico (escala modular, tracking negativo no display), microinteração em foco, hover e envio. PROTÓTIPO E AUTOCRÍTICA: toda tela nova ou alterada vira `1-design/prototipos/{tela}.html` autossuficiente, com os tokens REAIS do projeto lidos do CSS de tokens (o `design-system.md` pode estar velho), desktop e celular lado a lado em moldura de aparelho, com controles animado, estático e pausar. Sirva por HTTP local (o Playwright MCP recusa `file:`), tire print em 1280 e em 390 (`{tela}-1280.png`, `{tela}-390.png` ao lado do HTML), avalie contra as duas réguas (hierarquia, contraste, ritmo, celular, \"parece template?\"), corrija e repita até passar; o relatório lista as rodadas e o que cada uma corrigiu. PROIBIDO: número ou dado inventado em tela real (cena ilustrativa não traz métrica), cópia de marca de terceiros, efeito que quebra contraste AA. TELA DE DADOS: lista pagina, ordena e filtra no servidor e tem os três estados (vazio, carregando, erro); toda tela sai com o checklist de tela da morph-uiux preenchido, superfície incluída. REUSO ANTES DE CRIAR: componente reutilizável se BUSCA antes de se escrever — rode `morph-spec graph explain <Nome>` e confira `.morph/context/design-system.md` (sem grafo: `grep -rn`); criar um novo exige justificativa em uma linha no relatório da fatia, a mesma regra que já vale para token de design. 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."
|
|
295
298
|
},
|
|
296
299
|
"infra-engineer": {
|
|
297
300
|
"title": "Infrastructure / Jobs Engineer",
|
|
@@ -335,7 +338,7 @@
|
|
|
335
338
|
"model": "sonnet",
|
|
336
339
|
"maxTurns": 120,
|
|
337
340
|
"effort": "medium",
|
|
338
|
-
"persona": "Você cuida de infra: jobs assíncronos com Hangfire (apenas quando há razão real), deployment, observabilidade. Pensa em retry, idempotência, dead-letter, métricas. Não inventa infra — usa o que o stack já oferece. Operações no GitHub (issues, PRs, releases, Actions) sempre via `gh` CLI no Bash — o morph-spec não usa MCP do GitHub. Coolify sempre via SSH + curl na API local — não há MCP nem CLI. 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;
|
|
341
|
+
"persona": "Você cuida de infra: jobs assíncronos com Hangfire (apenas quando há razão real), deployment, observabilidade. Pensa em retry, idempotência, dead-letter, métricas. Não inventa infra — usa o que o stack já oferece. Operações no GitHub (issues, PRs, releases, Actions) sempre via `gh` CLI no Bash — o morph-spec não usa MCP do GitHub. Coolify sempre via SSH + curl na API local — não há MCP nem CLI. 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."
|
|
339
342
|
},
|
|
340
343
|
"verifier": {
|
|
341
344
|
"title": "Gate 3 Verifier",
|
|
@@ -365,7 +368,7 @@
|
|
|
365
368
|
],
|
|
366
369
|
"model": "sonnet",
|
|
367
370
|
"maxTurns": 90,
|
|
368
|
-
"persona": "Você é o verificador independente do Gate 3. Não participou da implementação. Sua missão é PROVAR cada cenário `C{n}` do `2-plan/checks.json` — e o default de todo cenário é NÃO PROVADO: só vira PROVADO com a evidência na mão. Para cada Cn: onde ele vale no código (`arquivo:linha`), qual teste o prova, e se esse teste MORDE — fiação (existe caminho da entrada real ao efeito: a camada pura testada não prova que o handler a chama; inventarie quem consome o eixo novo), teste que nasceu verde e afirma o que já era verdade, que só testa a recusa e nunca o caminho de escrita, guard cujos dois lados passam pelo mesmo serializador, asserção sobre texto que mede vocabulário e não sentido. Verificações mecânicas (.NET minimal API / Next): todo serviço injetado numa lambda de endpoint tem registro em `Program.cs`; prefixo de rota server-direct e de proxy são constantes distintas, cada uma do seu lado. Com o stack no ar (URL no dispatch), navegue o fluxo do cenário com Playwright e confira console e network. Stack fora do ar é achado de infra, não defeito seu a provar — não suba nem derrube o stack. Cenário que você não conseguiu verificar fica NÃO PROVADO com o motivo, nunca PROVADO por inferência. Siga a seção \"Verificador\" de `.morph/framework/evals/review-checklist.md` e grave `4-review/verifier.md` com `## Checks` (`| Cn | PROVADO|NÃO PROVADO|FALHA | evidência |`, uma linha por cenário) — mais `## Checklist de tela`, `## Mídia gerada` e `## Mutações` quando o checklist pede. Cenário NÃO PROVADO ou FALHA vira bloqueador `check` (ou `teste`). A suíte inteira roda fora de você (o verify do orquestrador, no CI ou local): nada de `morph-spec verify`, suíte completa ou build. Pode rodar UM filtro dos testes de um cenário, em primeiro plano, por `morph-spec heavy run -- <comando>`, e no máximo UMA rodada de `morph-spec mutate {feature}` (até 3 mutantes, cada um com `expect`) — e só depois que o dispatch disser que revisor e segurança terminaram: eles leem o mesmo repositório.
|
|
371
|
+
"persona": "Você é o verificador independente do Gate 3. Não participou da implementação. Sua missão é PROVAR cada cenário `C{n}` do `2-plan/checks.json` — e o default de todo cenário é NÃO PROVADO: só vira PROVADO com a evidência na mão. Para cada Cn: onde ele vale no código (`arquivo:linha`), qual teste o prova, e se esse teste MORDE — fiação (existe caminho da entrada real ao efeito: a camada pura testada não prova que o handler a chama; inventarie quem consome o eixo novo), teste que nasceu verde e afirma o que já era verdade, que só testa a recusa e nunca o caminho de escrita, guard cujos dois lados passam pelo mesmo serializador, asserção sobre texto que mede vocabulário e não sentido. Verificações mecânicas (.NET minimal API / Next): todo serviço injetado numa lambda de endpoint tem registro em `Program.cs`; prefixo de rota server-direct e de proxy são constantes distintas, cada uma do seu lado. Com o stack no ar (URL no dispatch), navegue o fluxo do cenário com Playwright e confira console e network. Stack fora do ar é achado de infra, não defeito seu a provar — não suba nem derrube o stack. Cenário que você não conseguiu verificar fica NÃO PROVADO com o motivo, nunca PROVADO por inferência. Siga a seção \"Verificador\" de `.morph/framework/evals/review-checklist.md` e grave `4-review/verifier.md` com `## Checks` (`| Cn | PROVADO|NÃO PROVADO|FALHA | evidência |`, uma linha por cenário) — mais `## Checklist de tela`, `## Mídia gerada` e `## Mutações` quando o checklist pede. Cenário NÃO PROVADO ou FALHA vira bloqueador `check` (ou `teste`). A prova é a do `prova:` do Cn: `leitura` se prova por `arquivo:linha` e fiação, sem teste — nunca peça teste para ele; só Cn com `risco:` exige teste que morde. A suíte inteira roda fora de você (o verify do orquestrador, no CI ou local): nada de `morph-spec verify`, suíte completa ou build. Pode rodar UM filtro dos testes de um cenário, em primeiro plano, por `morph-spec heavy run -- <comando>`, e no máximo UMA rodada de `morph-spec mutate {feature}` (até 3 mutantes, cada um com `expect`) — e só depois que o dispatch disser que revisor e segurança terminaram: eles leem o mesmo repositório. Sem subconjunto (exit 2) ou mutante `inconclusive`: prove por leitura, nunca a suíte inteira. A reversão do mutante é da ferramenta; rodada morta no meio: `morph-spec mutate --restore`. Arquivo você lê com Read, nunca `sed`/`cat`/`head`. 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`). O MCP `graphify` dá o grafo cru; a conclusão sai do `morph-spec graph`, e sem grafo (`no-graph`) caia para grep."
|
|
369
372
|
},
|
|
370
373
|
"reviewer": {
|
|
371
374
|
"title": "Gate 3 Reviewer",
|
|
@@ -394,7 +397,7 @@
|
|
|
394
397
|
],
|
|
395
398
|
"model": "sonnet",
|
|
396
399
|
"maxTurns": 60,
|
|
397
|
-
"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); 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.
|
|
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`)."
|
|
398
401
|
},
|
|
399
402
|
"security": {
|
|
400
403
|
"title": "Gate 3 Security Reviewer",
|
|
@@ -421,7 +424,7 @@
|
|
|
421
424
|
"mcp": [],
|
|
422
425
|
"model": "opus",
|
|
423
426
|
"maxTurns": 60,
|
|
424
|
-
"persona": "Você é a revisão de segurança do Gate 3, read-only, despachada só quando o diff toca auth, segredo, permissão, dado pessoal, pagamento ou migration. 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. Procure, na seção \"Segurança\" de `.morph/framework/evals/review-checklist.md`: authz faltando ou checada do lado errado, segredo no código, no log ou no cliente, injeção (SQL, comando, prompt), dado pessoal vazando para log, resposta ou provedor, custo sem teto (chamada paga em laço, sem limite por usuário), mídia gerada publicada sem revisão humana, migration destrutiva (DROP/rename de coluna com dado, branch Neon de produção). Bloqueador é tipo `seguranca` e exige o CAMINHO DE EXPLORAÇÃO: quem, por qual entrada, chega a quê. Sem caminho, é nota.
|
|
427
|
+
"persona": "Você é a revisão de segurança do Gate 3, read-only, despachada só quando o diff toca auth, segredo, permissão, dado pessoal, pagamento ou migration. 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. Procure, na seção \"Segurança\" de `.morph/framework/evals/review-checklist.md`: authz faltando ou checada do lado errado, segredo no código, no log ou no cliente, injeção (SQL, comando, prompt), dado pessoal vazando para log, resposta ou provedor, custo sem teto (chamada paga em laço, sem limite por usuário), mídia gerada publicada sem revisão humana, migration destrutiva (DROP/rename de coluna com dado, branch Neon de produção). Bloqueador é tipo `seguranca` e exige o CAMINHO DE EXPLORAÇÃO: quem, por qual entrada, chega a quê. Sem caminho, é 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`)."
|
|
425
428
|
},
|
|
426
429
|
"scout": {
|
|
427
430
|
"title": "Codebase Scout",
|
|
@@ -29,12 +29,12 @@ If the user did not pass a feature name in `$ARGUMENTS`:
|
|
|
29
29
|
## What you display
|
|
30
30
|
|
|
31
31
|
The CLI prints a banner, the phase chain (`plan → [uiux] → implement → review`),
|
|
32
|
-
output completion (X/N outputs), slice progress (reports in `3-implement/reports/`), and
|
|
32
|
+
output completion (X/N outputs), slice progress (reports in `3-implement/reports/`), and the `próximo:` line (the same rule as `morph-spec next`).
|
|
33
33
|
|
|
34
34
|
After running the CLI, summarize:
|
|
35
35
|
- Current phase and which gate (if any) is open
|
|
36
36
|
- Outstanding outputs for the current phase
|
|
37
|
-
-
|
|
37
|
+
- The `próximo:` line as printed — never a next step of your own
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
package/framework/docs/CLI.md
CHANGED
|
@@ -38,7 +38,7 @@ No **review**, o `gate-check` lê os relatórios de `4-review/` (`src/lib/review
|
|
|
38
38
|
|
|
39
39
|
Um stamp de `verification` com status `fail` (último `morph-spec verify` vermelho) **força pausa** em qualquer gate. Um stamp `pass` mas **stale** (o código mudou depois do commit do stamp — só conta o que está fora de `.morph/`; o `config.json` conta exceto a troca de `expectedCount`, e um commit só de estado, como o do próprio `feature.json` do verify, não invalida) força pausa **só no ship** (`review`); em `plan`/`uiux` a staleness é computada e reportada em `signals`, mas não pausa nada — é regra de ship, não de gate genérico. Stamp **ausente** nunca pausa por isso (features legadas sem stamp).
|
|
40
40
|
|
|
41
|
-
**Dispensa de e2e:** o stamp carrega `nodes.e2e` e a razão literal `e2eReason`, porque um nó e2e que **pula** não reprova o rollup — sem eles o gate lê `pass` tanto quando o e2e rodou e passou quanto quando ele nunca rodou. No **ship** (`review`), um `skip` é classificado pela razão (`classifyE2eSkip`, em `risk-detector.js` — fonte única) e o veredito fica registrado em `gateDecisions.review.signals` (`e2eStatus`, `e2eReason`, `e2eSkipClass`): `project` ("project does not containerize")
|
|
41
|
+
**Dispensa de e2e:** o stamp carrega `nodes.e2e` e a razão literal `e2eReason`, porque um nó e2e que **pula** não reprova o rollup — sem eles o gate lê `pass` tanto quando o e2e rodou e passou quanto quando ele nunca rodou. No **ship** (`review`), um `skip` é classificado pela razão (`classifyE2eSkip`, em `risk-detector.js` — fonte única) e o veredito fica registrado em `gateDecisions.review.signals` (`e2eStatus`, `e2eReason`, `e2eSkipClass`): `project` ("project does not containerize") e `not-applicable` ("not applicable: …", a feature sem cenário `prova: e2e` e sem arquivo de UI, spec e2e ou container no diff) são **dispensa legítima** — passam, com o motivo gravado, sem precisar de `gate-decision` manual; `environment` ("docker is not available…"), `requested` (`--skip-e2e`) e `unknown` (qualquer outra razão, **inclusive razão nenhuma**) **pausam** — a máquina ou o operador não são evidência sobre o código, e uma dispensa sem motivo é o mesmo no-op silencioso que a regra existe para eliminar. `e2eStatus`/`e2eReason`/`e2eSkipClass` **não** são sobrescrevíveis por `--signals`: descrevem o que aconteceu no disco, como `gate`/`workType`.
|
|
42
42
|
|
|
43
43
|
**Prova de mídia:** do MESMO relatório e pelo MESMO leitor, o `gate-check` grava `signals.mediaClaims = {section, featureGeneratesMedia}`. `featureGeneratesMedia` é determinístico do disco — existe ao menos uma fatia do `checks.json` cujo `standards` cita `ai-agents-modalities-image-gen`, `ai-agents-media-video` ou `ai-agents-media-pipeline` — pelo id de STANDARDS.json ou pelo caminho, com ou sem `framework/`/`standards/` na frente, porque uma grafia não prevista desarmaria o guardrail sem uma linha de erro (`featureGeneratesMedia`, função exportada de `gate-check.js`, é onde essa normalização mora e é testada). Nessa condição, relatório **sem** a seção `## Mídia gerada` **pausa o Gate 3**: as quatro afirmações de mídia (custo, revisão humana, marcação, hot path não bloqueado) não foram provadas (`framework/evals/review-checklist.md`, "Feature que gera mídia").
|
|
44
44
|
|
|
@@ -58,7 +58,7 @@ A tabela é a mesma régua que `shouldPauseGate` aplica e que o `gate-check` imp
|
|
|
58
58
|
| `verificationStatus` | === 'fail' | `verify` vermelho — build, testes, validadores **ou e2e**. Vale em **qualquer** gate |
|
|
59
59
|
| `verificationStatus` | ausente, no `review` | nenhum `verify` carimbado: ship sem prova; `approve` auto aplica a mesma régua |
|
|
60
60
|
| `verificationStale` | true | Stamp do `verify` com sha != HEAD: o verde é de outro código. Só no **ship** |
|
|
61
|
-
| `e2eSkipClass` (derivada) | environment/requested/unknown | e2e pulou sem dispensa legítima (
|
|
61
|
+
| `e2eSkipClass` (derivada) | environment/requested/unknown | e2e pulou sem dispensa legítima (`project` e `not-applicable` passam). Só no **ship** |
|
|
62
62
|
| `promptModel` | prompt/modelo tocado | Sem nó `evals` declarado, ou eval pulado neste verify. Só no **ship** |
|
|
63
63
|
| `mediaClaims` | seção ausente | Feature que gera mídia, relatório sem `## Mídia gerada`. Só no **ship** |
|
|
64
64
|
| `screenChecklist` | item em aberto | **uiux**: tela sem checklist completo, nenhuma `## Tela:`, ou tela sem protótipo e prints 1280/390. **ship**: item decidido sem linha em `## Checklist de tela` |
|
|
@@ -262,6 +262,10 @@ Fail-open: uma poda que falha nunca derruba o comando. PROVE-or-DECLINE como o r
|
|
|
262
262
|
|
|
263
263
|
**`morph-spec handoff <feature> [--here]`** — regenera o documento de retomada (`.morph/features/{f}/handoff.md`): comprimido, derivado, aponta para os artefatos em vez de copiá-los. **Resolve a árvore dona** antes de gerar (mesma lógica do `list`/`fleet`) e escreve LÁ, não necessariamente no cwd — rodar da raiz com o worktree vivo é seguro: o handoff sai correto e no lugar certo, em vez do esqueleto de nascimento da raiz. Sem árvore nenhuma materializada (só existe commitado em `morph/{f}`) recusa com `feature-not-in-tree`, nomeando a branch, em vez de gerar um documento apontando para arquivos inexistentes. `--here` força construir/escrever no cwd mesmo assim. Gerado automaticamente em `setup`/`create --worktree`/`advance`/`approve`.
|
|
264
264
|
|
|
265
|
+
### next
|
|
266
|
+
|
|
267
|
+
**`morph-spec next <feature> [--json]`** — o próximo passo do loop, decidido do disco na árvore dona (mesma resolução do `handoff`). Somente leitura. Ações: `plan` (sem `checks.json`), `gate` (plano extraído), `design` (uiux), `dispatch` (fatias prontas, com as `PARCIAL` nomeadas para retomar), `decide` (nenhuma pronta e alguma com relatório `BLOQUEADO`: decida pelo plano/ADR ou `AskUserQuestion` e retome o mesmo agente, nunca re-despache), `blocked` (pendentes sem dependência satisfeita), `verify` (todas relatadas e sem stamp, ou a árvore andou desde ele — `treeMovedSince`, a régua do `gate-check`), `fix`, `escalate`, `review`, `finish`. **Escalada:** `verify` persistido grava `verifications.feature.failStreak` (vermelhos seguidos; verde zera; rodada parcial ou `--no-persist` não conta); com o stamp vermelho e a árvore parada, `failStreak` múltiplo de 3 é `escalate` → `AskUserQuestion` (aceitar/continuar/parar); o `reason` diz o que cada resposta faz (aceitar → review, onde o verify vermelho pausa o gate e o humano assina). A resposta não é gravada: o `escalate` **persiste até a árvore andar** — releia a resposta da conversa em vez de perguntar de novo. "Continuar" segue de onde parou: a próxima escalada vem 3 vermelhos depois. **Por tipo:** `hotfix` troca toda skill pela `/morph-hotfix` (o pipeline inteiro mora lá); `chore`/`hotfix` nascem em implement sem `checks.json` → `plan` (plano curto e `plan-check`). A regra é UMA (`decideNext`, `framework/hooks/shared/next-step.js`) e tem três leitores: este comando, o `status` e o `handoff`, e o SessionStart, que injeta a linha `▶ próximo:` sem git (verde lido como fresco) — e por isso nunca injeta `escalate` (pede `morph-spec next`) nem o próximo passo do esqueleto da raiz de uma feature que vive num worktree (aponta o worktree). `--json`: `{ action, command, reason, slices?, phase, failStreak, feature, tree }`.
|
|
268
|
+
|
|
265
269
|
### fleet
|
|
266
270
|
|
|
267
271
|
**`morph-spec fleet`** — visão da frota: toda feature ativa (raiz + worktrees) com fase, gates pendentes, tasks, **remoto** (`local`/`pushed`/`PR #N`), **claim** (que sessão está na feature), portas, handoff e atividade. Use entre sessões para decidir onde atuar.
|
|
@@ -282,6 +286,8 @@ O nó `e2e` é o único que **executa** a aplicação: sobe o `docker compose` d
|
|
|
282
286
|
|
|
283
287
|
As specs são procuradas na raiz, em `project.frontendPath` e, sem ele, nas pastas de frontend conhecidas (as mesmas do detector, mais `web`). Se o nó cai para smoke enquanto existe um `playwright.config.*` não executado em pasta de primeiro nível, o `verify` avisa nomeando a pasta e `project.frontendPath`. A contagem de specs sai em `nodes.e2e.count` do `verify --json` (`null` quando não rodou ou o sumário é ilegível, nunca 0); ela não é persistida no stamp.
|
|
284
288
|
|
|
289
|
+
**O e2e só sobe quando pode provar algo:** com testes vermelhos ele não roda (`tests failed — the e2e does not run over a red suite`); sem nó de teste `e2e: true` (esses são a prova do backend contra a stack e sempre a sobem), ele também não roda quando nenhum cenário do `checks.json` é `prova: e2e` e o diff da feature contra a branch default (fora de `.morph/`) não toca UI (`.tsx`, `.css`, `.html`…), spec e2e, `playwright.config.*`, Dockerfile/compose nem pasta de frontend conhecida — `skip` com a razão `not applicable: …`, que o gate aceita. Sem git, sem base ou diff vazio: roda (só para diante de evidência).
|
|
290
|
+
|
|
285
291
|
**Dispensa legítima × setup faltando:** projeto sem compose e sem a integração `docker` é `skip` com razão; projeto que **declara** `docker` e não tem compose é `fail` — é verificação que nunca foi montada, não projeto que não containeriza.
|
|
286
292
|
|
|
287
293
|
**Descoberta de testes e relógio:** a varredura por `*Tests.csproj` **não enxerga worktrees** — nem os convencionais (`worktrees/`, `.worktrees/`) nem os registrados em `git worktree list` fora da convenção. O timeout do nó `tests` deixou de ser fixo: `--timeout` (que agora cobre build **e** tests) > `project.testTimeoutMs` no `config.json` > **120 s por projeto de teste encadeado** — um encadeamento nunca nasce condenado pelo tamanho. Quando o relógio estoura, o nó diz isso com nome (`timeout após N ms na execução X de Y — nenhum teste falhou`) em vez de devolver log truncado.
|
|
@@ -316,7 +322,7 @@ Nenhuma regra de gate muda: e2e pulado, escopo, evals e stale valem igual.
|
|
|
316
322
|
|
|
317
323
|
**Sem workflow** que rode `morph-spec verify` publicando `morph-verify.json`, cria e commita `.github/workflows/morph-verify.yml` na branch da feature (gatilho `push` em `morph/**`, `.morph/` fora do gatilho exceto o `config.json`, `cancel-in-progress`, setup de .NET por `global.json`/stack, install por lockfile, browsers do Playwright onde há config, e o `morph-spec` na versão de `.morph/.morphversion`). Um run sobre um commit anterior vale quando só `.morph/` andou desde ele; run `cancelled`/`skipped`/`startup_failure` não é prova.
|
|
318
324
|
|
|
319
|
-
**Sem CI cai para o verify local, com a razão** (`project.ci: false`, origin fora do GitHub, `gh` ausente ou sem login, branch que não é `morph/<feature>`, token HTTPS sem o escopo `workflow` para criar o arquivo, push recusado, workflow que não disparou nem com `gh workflow run`, run sem o artefato). **Antes de
|
|
325
|
+
**Sem CI cai para o verify local, com a razão** (`project.ci: false`, origin fora do GitHub, `gh` ausente ou sem login, branch que não é `morph/<feature>`, token HTTPS sem o escopo `workflow` para criar o arquivo, push recusado, workflow que não disparou nem com `gh workflow run`, run sem o artefato). **Antes de tudo — com ou sem CI — reaproveita o stamp fresco:** último stamp `pass` com `sha`, nada fora de commit e `treeMovedSince(stamp.sha, HEAD)` falso (a mesma régua do `verificationStale`: só `.morph/` andou, ou o `config.json` mudou só `expectedCount`) → não empurra, não roda nem regrava, imprime "stamp verde e fresco — o CI não roda de novo" e o `--json` traz `reason: "stamp-reused"`, `status`, `nodes` e `reused: { sha, ts, source }`, exit 0.
|
|
320
326
|
|
|
321
327
|
Código não commitado é **erro de uso (exit 2)**: o CI só vê commits. CI rodando depois de 50 min é **exit 3** (`status: "pending"`), sem stamp — não é vermelho. Com task, `--filter`, `--skip-*`, `--fresh-e2e`, `--linux`, `--record-baseline`, `--refresh-cache` ou `--no-persist`, erro de uso. Vermelho imprime `gh run view <id> --log-failed`.
|
|
322
328
|
|
|
@@ -328,25 +334,25 @@ Código não commitado é **erro de uso (exit 2)**: o CI só vê commits. CI rod
|
|
|
328
334
|
|
|
329
335
|
**Validador `code-budget` (nó `validators` do `verify`)** — mede o que a árvore **adicionou** contra a branch default: `git diff -U0` a partir do merge-base com `origin/{default}` (ou `{default}` local), arquivo novo não rastreado conta inteiro e arquivo movido sem edição não conta. Lê `.cs`/`.ts`/`.tsx`/`.js`/`.jsx`/`.mjs`/`.cjs`, fora de `.d.ts`, de `node_modules`/`bin`/`obj`/`dist`/`.morph` e de código gerado (`*.Designer.cs`, `*.g.cs`, `*ModelSnapshot.cs`).
|
|
330
336
|
|
|
331
|
-
Três medidas, cada uma **um aviso, nunca erro**: (a) **linhas de comentário adicionadas que citam processo** — `UC-nn`, `ADR-nn`, `FR-nn`, `C{n}`/`F{n}` (cenário/fatia — fora tecla, métrica, célula e o modelo C4), `Gate N`, `plan.md`, `checks.json`, mockup — avisa a partir de uma, com a contagem sobre o total de linhas de comentário adicionadas e até 4 `arquivo:linha` (standard `architecture-code-comments`); comentário é o que o varredor de `src/lib/ai/comment-stripper.js` separa de string, com template e regex literal de TS/JS; (b) **linhas não vazias de teste ÷ de produção** — avisa acima de **1
|
|
337
|
+
Três medidas, cada uma **um aviso, nunca erro**: (a) **linhas de comentário adicionadas que citam processo** — `UC-nn`, `ADR-nn`, `FR-nn`, `C{n}`/`F{n}` (cenário/fatia — fora tecla, métrica, célula e o modelo C4), `Gate N`, `plan.md`, `checks.json`, mockup — avisa a partir de uma, com a contagem sobre o total de linhas de comentário adicionadas e até 4 `arquivo:linha` (standard `architecture-code-comments`); comentário é o que o varredor de `src/lib/ai/comment-stripper.js` separa de string, com template e regex literal de TS/JS; (b) **linhas não vazias de teste ÷ de produção** — avisa acima de **1×**, e só quando o teste adicionado passa de **40 linhas** (a regressão de um bug de duas linhas cabe abaixo disso); sem linha de produção, o aviso dá as duas contagens sem razão. (c) **densidade de comentário** — linhas de comentário adicionadas ÷ linhas de código adicionadas (as não vazias menos as de comentário), doc-comment (`///`, JSDoc) incluído — avisa acima de **1 a cada 10**, e só a partir de **40 linhas** de código, com até 4 arquivos mais comentados e suas contagens (o padrão do standard `architecture-code-comments` é nenhum comentário).
|
|
332
338
|
|
|
333
339
|
Teste é o que o validador `reuse` já trata como teste: arquivo `.test.` ou `.spec.` com extensão `js`/`jsx`/`ts`/`tsx`, e tudo sob `__tests__/`, `test/`, `tests/` ou pasta terminada em `Test`/`Tests` (`Foo.Tests/`, `FooTests/`). O resto conta como produção, inclusive `a.test.mjs`, `a.test.cjs` e `*.e2e.ts` fora dessas pastas.
|
|
334
340
|
|
|
335
|
-
Sem base git, ou sem linha de código adicionada, pula com razão; qualquer falha de medição também vira skip, nunca erro.
|
|
341
|
+
Sem base git, ou sem linha de código adicionada, pula com razão; qualquer falha de medição também vira skip, nunca erro.
|
|
336
342
|
|
|
337
343
|
**Por quê.** Excesso justificado não silencia o aviso: a justificativa mora na task (orçamento do `morph-plan` passo 5), e é lá que o avaliador a lê.
|
|
338
344
|
|
|
339
345
|
### mutate
|
|
340
346
|
|
|
341
|
-
**`morph-spec mutate [feature] [--plan <arquivo>] [--out <arquivo>] [--
|
|
347
|
+
**`morph-spec mutate [feature] [--plan <arquivo>] [--out <arquivo>] [--allow-dirty] [--jobs <n>] [--timeout <ms>] [--restore] [--json]`** — mutação **dirigida** para o verificador do Gate 3: no máximo **3** mutantes, só quando o diff tem uma guarda que um cenário exige e o verificador não achou o teste que a morde.
|
|
342
348
|
|
|
343
349
|
**Manifesto** (`--plan`, default `4-review/mutants.json`): `{"mutants":[{"id":"M1","file":"src/x.cs","line":42,"find":"a && b","replace":"a","expect":"OrdersTests.RecusaSemItens deve falhar"}]}` — `expect` é obrigatório (qual teste deve falhar e por quê, dito antes de rodar), `find` começa na `line` e é único nela; barra invertida do código vai como `{{BS}}` (o transporte do Bash a reduz). Mais de 3 mutantes ou `expect` ausente: exit 2.
|
|
344
350
|
|
|
345
351
|
**O que compila (.NET):** o `build` do nó quando declarado; senão o que o comando nomeia (`.sln`/`.slnx`/`.csproj` — a solution vale pelos projetos que lista, também para o subconjunto); senão a solution única (ou o projeto único) do diretório alvo, como o próprio `dotnet test` escolhe. Só sem nada descobrível recusa (exit 2), antes de mutar, pedindo a solution no comando ou o `build`.
|
|
346
352
|
|
|
347
|
-
**Método:** recusa arquivo não commitado (`--allow-dirty` assume o risco); snapshot dos bytes fora da árvore (`<git-common-dir>/morph-spec/mutation/<run>/`) com journal — rodada que morreu se desfaz por `mutate --restore`; em .NET, uma chave `MORPH_MUTANT` por mutante de expressão e um build só (o que a chave não compila cai para rebuild real, e falha de build é `build-error`, nunca morte); depois **o subconjunto decide**: os testes que a feature alterou mais os que citam os tipos mutados (vitest/jest usam `related`) — a ferramenta escolhe, não o verificador. `killed` = falha **nova** contra a rodada-base do mesmo escopo, com o motivo literal do TRX; `survived` no subconjunto é o veredito, com o `expect` ao lado;
|
|
353
|
+
**Método:** recusa arquivo não commitado (`--allow-dirty` assume o risco); snapshot dos bytes fora da árvore (`<git-common-dir>/morph-spec/mutation/<run>/`) com journal — rodada que morreu se desfaz por `mutate --restore`; em .NET, uma chave `MORPH_MUTANT` por mutante de expressão e um build só (o que a chave não compila cai para rebuild real, e falha de build é `build-error`, nunca morte); depois **o subconjunto decide**: os testes que a feature alterou mais os que citam os tipos mutados (vitest/jest usam `related`) — a ferramenta escolhe, não o verificador. `killed` = falha **nova** contra a rodada-base do mesmo escopo, com o motivo literal do TRX; `survived` no subconjunto é o veredito, com o `expect` ao lado; rodada que cai é `inconclusive`. **Nunca a suíte inteira:** sem subconjunto utilizável (nenhum teste da feature nem citando os tipos mutados) o comando recusa antes de compilar (exit 2) e manda provar o cenário por leitura; subconjunto que some em runtime (base que não seleciona teste, ou cai) deixa os mutantes `inconclusive` com a mesma razão.
|
|
348
354
|
|
|
349
|
-
**Nó `e2e: true`:** arquivo coberto **só** por nó de teste `e2e: true` é recusado (exit 2, nomeando mutante, arquivo e nó): a suíte testa o container do compose e o `mutate` recompila só localmente — todo mutante sobreviveria em falso. Com outro nó dono do arquivo, ele é o escolhido — mas, havendo qualquer nó `e2e: true` no config, só se algum arquivo de teste dele (com atributo de teste) citar um tipo declarado no arquivo mutado: o dono de um `.cs` é a suíte .NET, não a referência de projeto, e a unidade que só referencia a API daria sobrevivente em falso. Sem citação, recusa (exit 2) antes do snapshot. Nas duas recusas a saída prescreve o protocolo manual com o comando pronto do nó e2e, cada passo numa linha só encadeado por `&&` (nunca `;`): base verde antes do 1º mutante (`eval "$(morph-spec env <f>)" && morph-spec e2e up <f> && morph-spec heavy run -- <comando do nó>`), a mesma cadeia depois de cada edição, e restauração conferida por `git diff --quiet` seguida de um novo `e2e up` — registrado em `4-review`. Sem nó `e2e: true`,
|
|
355
|
+
**Nó `e2e: true`:** arquivo coberto **só** por nó de teste `e2e: true` é recusado (exit 2, nomeando mutante, arquivo e nó): a suíte testa o container do compose e o `mutate` recompila só localmente — todo mutante sobreviveria em falso. Com outro nó dono do arquivo, ele é o escolhido — mas, havendo qualquer nó `e2e: true` no config, só se algum arquivo de teste dele (com atributo de teste) citar um tipo declarado no arquivo mutado: o dono de um `.cs` é a suíte .NET, não a referência de projeto, e a unidade que só referencia a API daria sobrevivente em falso. Sem citação, recusa (exit 2) antes do snapshot. Nas duas recusas a saída prescreve o protocolo manual com o comando pronto do nó e2e, cada passo numa linha só encadeado por `&&` (nunca `;`): base verde antes do 1º mutante (`eval "$(morph-spec env <f>)" && morph-spec e2e up <f> && morph-spec heavy run -- <comando do nó>`), a mesma cadeia depois de cada edição, e restauração conferida por `git diff --quiet` seguida de um novo `e2e up` — registrado em `4-review`. Sem nó `e2e: true`, vale a regra do subconjunto acima.
|
|
350
356
|
|
|
351
357
|
**Sem `<feature>`:** vale a única feature em andamento; sem ela (nenhuma, ou mais de uma), exige `--plan`, e a seção só é gravada com `--out` — sai no stdout, `section: null` no `--json`.
|
|
352
358
|
|
|
@@ -470,7 +476,7 @@ A auditoria devolve **nada** sem tentar coisa alguma em três situações: nenhu
|
|
|
470
476
|
|
|
471
477
|
**`morph-spec report-check <feature> [F1 F2 ...] [--before-eval] [--json]`** — audita o relatório de cada fatia (`3-implement/reports/F{n}.md`) contra o trabalho que realmente entrou.
|
|
472
478
|
|
|
473
|
-
Vereditos: `ok`; `report-stale` → atualize a partir de `observed`, NÃO re-despache; `report-incomplete` com `partial: true` (relatório com `PARCIAL` na 1ª linha — o checkpoint a ~70% do `maxTurns`) e ação `resume`: retome o mesmo agente, NÃO carimbe DEGRADADO; `report-incomplete` sem marcador → `complete-degraded`; `work-landed-report-missing` → aceite degradado, NÃO re-despache; `no-work-no-report` → re-despache uma vez; `report-missing-evidence-unknown` quando não há telemetria. O marcador é lido de propósito, nunca inferido da falta de um item.
|
|
479
|
+
Vereditos: `ok`; `report-stale` → atualize a partir de `observed`, NÃO re-despache; `report-incomplete` com `partial: true` (relatório com `PARCIAL` na 1ª linha — o checkpoint a ~70% do `maxTurns`) e ação `resume`: retome o mesmo agente, NÃO carimbe DEGRADADO; `report-blocked` (relatório com `BLOQUEADO` na 1ª linha — decisão que não é do agente, com a pergunta e as opções) e ação `ask-human`: NÃO re-despache; decida pelo plano/ADR ou pergunte por `AskUserQuestion` e retome o mesmo agente com a resposta (a fatia fica fora das prontas e o `advance` do implement recusa); `report-incomplete` sem marcador → `complete-degraded`; `work-landed-report-missing` → aceite degradado, NÃO re-despache; `no-work-no-report` → re-despache uma vez; `report-missing-evidence-unknown` quando não há telemetria. O marcador é lido de propósito, nunca inferido da falta de um item.
|
|
474
480
|
|
|
475
481
|
**Evidência por agente:** `observed` vem do `events.jsonl` de toda árvore — o dono da fatia é o `agentId` do último `Write` do relatório, e contam os writes desse agente; sem `agentId` (relatório inline do orquestrador) contam os writes aos `arquivos:` da fatia. Writes do orquestrador sob `.morph/` não contam. Sem fatia na linha de comando, audita toda fatia do `checks.json`.
|
|
476
482
|
|
|
@@ -494,7 +500,11 @@ Sem fatia na linha de comando, a saída traz `reviews: { verifier, reviewer, sec
|
|
|
494
500
|
|
|
495
501
|
### stack-scan
|
|
496
502
|
|
|
497
|
-
**`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`) são **Alto
|
|
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; classe não-sealed 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
|
+
|
|
505
|
+
### tests-audit
|
|
506
|
+
|
|
507
|
+
**`morph-spec tests-audit [--json]`** — auditoria **somente leitura** dos arquivos de teste rastreados (`git ls-files` + `isTestPath`): total de casos (`[Fact]`/`[Theory]`, `it`/`test`), os maiores arquivos e quatro listas de **candidatos** — nunca vereditos, nada é apagado: teste que lê `.md`/`.txt` ou tem 3+ asserts de frase literal (fixa texto, não comportamento); constante comparada ao próprio literal; família de 4+ `[Fact]` com o mesmo prefixo de nome (candidata a `[Theory]`); o mesmo assunto em mais de uma camada (`XHandlerTests` e `XEndpointTests`/`XIntegrationTests` — fique com a mais barata que morde). Fora de git: exit 2 com a razão. É o ponto de partida de uma limpeza: apagar teste e depois rodar `verify --record-baseline` grava a contagem menor.
|
|
498
508
|
|
|
499
509
|
## Suítes de teste (`project.tests[]`) e a divisão loop × suíte
|
|
500
510
|
|
|
@@ -677,7 +687,7 @@ sem `--no-build` já compila sozinho, e pré-compilar ali só dobraria o relógi
|
|
|
677
687
|
**O ciclo da baseline, e o número que só sai à mão.** `verify --record-baseline` é a única via de
|
|
678
688
|
escrita: mede cada nó numa execução real e grava `expectedCount` de volta no `config.json` (escrita
|
|
679
689
|
atômica `tmp.{pid}` + rename, preservando todo o resto do arquivo). Recusa **quatro** vezes — execução
|
|
680
|
-
vermelha, execução **parcial** (sob `--filter`, mesmo com o verify verde: baseline de meia frota é
|
|
690
|
+
vermelha (fora o `count-drop` com exit 0, abaixo), execução **parcial** (sob `--filter`, mesmo com o verify verde: baseline de meia frota é
|
|
681
691
|
afirmação sobre a outra metade que ninguém fez), contagem desconhecida, e **sem `config.json` ou sem
|
|
682
692
|
`project.tests[]`** (erro de uso, exit 2; o arquivo nunca é criado implicitamente — gravar os nós que o
|
|
683
693
|
fallback sintetiza exigiria inventar a declaração que o comando veio manter). Verde com gravação
|
|
@@ -685,11 +695,7 @@ recusada sai **exit 1**, com a razão em **stderr** (sobrevive ao `--json`). A g
|
|
|
685
695
|
pelos **nós de teste**, não pelos quatro nós do verify: um `validators` vermelho não muda quantos
|
|
686
696
|
testes rodaram. A execução que **gravou** a baseline (verde e inteira) persiste o stamp `verification` como qualquer verify: ela mediu a suíte toda, e rodá-la de novo só para ter prova seria pagar duas vezes pelo mesmo
|
|
687
697
|
verde. Sem gravação (vermelha, parcial, contagem desconhecida) não há stamp, e o `reason` do `--json`
|
|
688
|
-
nomeia o nó que impediu. **
|
|
689
|
-
mão **alto demais** (9 onde a suíte roda 7) deixa o verify vermelho por `count-drop`, e então
|
|
690
|
-
`--record-baseline` recusa — preservando o 9. **Não há flag de regravação forçada:** rebaixe ou apague
|
|
691
|
-
o número à mão e então re-meça pelo comando. O caso oposto se conserta sozinho — número baixo demais
|
|
692
|
-
passa com aviso de alta, e o comando grava.
|
|
698
|
+
nomeia o nó que impediu. **Apagar teste é legítimo:** a única cor vermelha que a gravação aceita é o `count-drop` de um nó que saiu com exit 0 — a suíte ficou menor de propósito. `--record-baseline` grava o número menor que mediu, o nó fica verde nessa mesma execução, o stamp persiste e o `feedback` diz `[baseline] rebaixada: <nó> (9 → 7)`; o revisor do Gate 3 vê a queda e julga se foi limpeza. Sem a flag, a queda continua reprovando (regressão silenciosa). Queda ao lado de outro nó vermelho não grava nada.
|
|
693
699
|
|
|
694
700
|
**A divisão loop × suíte.** É a resposta ao "a suíte inteira não cabe numa chamada":
|
|
695
701
|
|
|
@@ -816,6 +822,7 @@ O servidor MCP (`graphify-mcp`, 10 tools, todas aceitando `project_path`) está
|
|
|
816
822
|
| suíte .NET que recompila | `dotnetFullSuite`: `dotnet test` sem `--filter` nem `--list-tests`, quantos sem `--no-build`, por fase |
|
|
817
823
|
| git | ocorrências de `git status` e `git diff` na janela |
|
|
818
824
|
| reuse-validator | por árvore (raiz × worktree), dos eventos `Verify`: rodou, pulou (e por quê) ou ausente |
|
|
825
|
+
| gates | por gate, dos eventos `Gate`: `decisions`, `paused`, `autoPassed`, `unapproved`, e o destino de cada pausa — `approvedAfterPause` (o humano aprovou sem nova decisão: o risco foi aceito como estava) ou `reworkedAfterPause` (uma decisão seguinte já não pausou pelo mesmo sinal: o sinal levou a mudança). `bySignal` dá os três por sinal (`signal` de `shouldPauseGate`, ou `manual`). Rodar o `gate-check` de novo sobre a mesma pausa não conta como retrabalho. Sinal com muito `approvedAfterPause` e pouco `reworkedAfterPause` é candidato a afrouxar — a decisão é humana, o número só a informa |
|
|
819
826
|
|
|
820
827
|
Os eventos v1 (sem `schemaVersion`) nunca contam como zero: sem agente, exit code ou duração, eles caem em "sem agente"/`unknown-*`/"sem duração", e o cabeçalho diz que fração da janela está no esquema v2.
|
|
821
828
|
|
|
@@ -828,6 +835,7 @@ Os eventos v1 (sem `schemaVersion`) nunca contam como zero: sem agente, exit cod
|
|
|
828
835
|
| `PostToolUse` / `PostToolUseFailure` | `tool`, `toolUseId`, `durationMs`; Bash/PowerShell: `command` (redigido, ≤2000 chars) + `commandChars`, `exitCode`, `outcome` (`ok`/`error`/`timeout`/`interrupted`/`background`), `exitMasked` quando o exit gravado não é o do trabalho (pipe sem `pipefail`, `|| …`, `&` em background, ou `;` seguido de `echo`/`true`/`:` no fim); Read: `target`, `kind`, `bytes` (tamanho do arquivo), `offset`, `limit`, `bytesRead` — um Read que falhou não grava bytes, porque não trouxe nada; Agent: `persona`, `desc`, `prompt` + `promptChars`; SendMessage: `target`, `messageChars` |
|
|
829
836
|
| `ToolBatch` | `size`, `tools` (contagem por ferramenta) — um por turno do modelo |
|
|
830
837
|
| `SessionUsage` / `AgentUsage` | totais acumulados do transcript + `delta` desde a amostra anterior |
|
|
838
|
+
| `Gate` | `gate`, `step` (`decision`/`approve`/`unapprove`); na `decision`: `pause`, `signal` (a regra que pausou, `null` sem pausa, `manual` pelo `gate-decision`) e `source`; nas outras: `approvedBy`. Gravado por `persistGateDecision` e pelos dois escritores de aprovação (CLI e hook `state-sync`) |
|
|
831
839
|
| `Verify` | `status`, `sha`, `fingerprint` (a árvore fora de `.morph/`), `tree` (`root`/`worktree`), `persisted`, `reason`, `source` (`local`/`ci`: a rodada do `--ci` registra os nós e durações do run), `nodes.{build,tests,validators,e2e}.{status,durationMs}`, `nodes.validators.{ran,skipped}` |
|
|
832
840
|
|
|
833
841
|
Nada da saída de um comando é gravado — nem o corpo de erro do `PostToolUseFailure`, onde um valor de `.env` pode aparecer sem nome ao lado. `command`, `prompt`, `desc` e `target` passam por redação de segredos óbvios antes de gravar: valor ao lado de um nome de segredo (`API_KEY=…`, `*_KEY`/`*_PASS=…`, `--password …`, `user-secrets set <k> <v>`, `AccountKey=…`), `Authorization: …`, `user:pass@` (senha com `/` inclusive) e `?sig=`/`?access_token=` em URL, e formatos de token (GitHub, `sk-`, `sk_live_`, `npm_`, Slack, AWS, Google, JWT). É por padrão, não por garantia: um valor sem nome ao lado passa.
|
|
@@ -41,7 +41,10 @@ diferente") é nota; nota não bloqueia e não pede rodada nova. Na dúvida, sem
|
|
|
41
41
|
## Verificador (`4-review/verifier.md`)
|
|
42
42
|
|
|
43
43
|
Missão: **provar cada cenário** do `2-plan/checks.json`. O default de todo `Cn` é NÃO PROVADO;
|
|
44
|
-
critério que você não verificou nunca vira PROVADO por inferência.
|
|
44
|
+
critério que você não verificou nunca vira PROVADO por inferência. A prova é a que o plano declarou
|
|
45
|
+
(`prova:` do Cn): `leitura` prova-se pelo `arquivo:linha` e pela fiação, **sem teste** — Cn `leitura`
|
|
46
|
+
sem teste não é NÃO PROVADO, e pedir teste para ele não é bloqueador. Só Cn com `risco:` exige teste
|
|
47
|
+
que morde.
|
|
45
48
|
|
|
46
49
|
```markdown
|
|
47
50
|
## Checks
|
|
@@ -82,7 +85,9 @@ morde. Escreva `4-review/mutants.json` e rode `morph-spec mutate {feature}` **um
|
|
|
82
85
|
|
|
83
86
|
`expect` é obrigatório: diga qual teste deve falhar e por quê **antes** de rodar — mutante cujo
|
|
84
87
|
efeito você não sabe prever tende a ser equivalente. Barra invertida do código vira `{{BS}}`.
|
|
85
|
-
Sobreviveu numa linha que um cenário exige → bloqueador `teste`. Sobreviveu em outra → nota.
|
|
88
|
+
Sobreviveu numa linha que um cenário exige → bloqueador `teste`. Sobreviveu em outra → nota.
|
|
89
|
+
Sem subconjunto (exit 2) ou `inconclusive`: a mutação não roda a suíte inteira — prove por leitura
|
|
90
|
+
(`arquivo:linha` + o teste que cobre), nunca em laço. Cole a
|
|
86
91
|
seção `## Mutações` que a ferramenta imprime. A reversão é da ferramenta; rodada morta no meio:
|
|
87
92
|
`morph-spec mutate --restore`.
|
|
88
93
|
|
|
@@ -109,6 +114,11 @@ Read-only, sobre o commit do dispatch: `git diff {base}..{sha}`, `git show {sha}
|
|
|
109
114
|
árvore de trabalho — o verificador pode estar com um mutante aplicado nela. Default SHIP.
|
|
110
115
|
|
|
111
116
|
1. **Escopo**: o diff contra as fatias (`arquivos:`) e o `## Contrato de implementação` do plano.
|
|
117
|
+
Teste além do orçamento também é escopo: teste para Cn `prova: leitura`, mais de um teste por Cn de
|
|
118
|
+
risco sem porquê no plano, teste que só fixa texto/constante/DTO, o mesmo comportamento em duas
|
|
119
|
+
camadas — bloqueador `escopo` (apague, não acrescente). `expectedCount` menor no
|
|
120
|
+
`.morph/config/config.json` do diff sem teste apagado no mesmo diff é bloqueador `regressao`: é
|
|
121
|
+
suíte que parou de rodar, não limpeza.
|
|
112
122
|
2. **Arquitetura**: padrão ou abstração nova contra o stack do projeto e os standards (camada que o
|
|
113
123
|
projeto não usa, repositório sobre o DbContext, dependência nova sem decisão no plano).
|
|
114
124
|
3. **Regressão**: para cada símbolo público alterado, `morph-spec graph affected <símbolo> --prod --json`.
|
|
@@ -119,6 +129,9 @@ Read-only, sobre o commit do dispatch: `git diff {base}..{sha}`, `git show {sha}
|
|
|
119
129
|
5. **Performance só com evidência**: lista sem paginação no servidor sobre volume real, N+1 visível.
|
|
120
130
|
6. **Chamada a LLM**: dá para reconstruir o que foi ao provedor e o que ele respondeu neste turno?
|
|
121
131
|
Não → nota.
|
|
132
|
+
7. **Tela no diff**: cor ou espaçamento literal fora do CSS de tokens, mais de uma ação primária,
|
|
133
|
+
erro sem próxima ação, conteúdo gerado por IA sem marca, foco invisível → nota. Bloqueia só pelo
|
|
134
|
+
contrato de tela do verificador.
|
|
122
135
|
|
|
123
136
|
## Segurança (`4-review/security.md`)
|
|
124
137
|
|
|
@@ -6,41 +6,16 @@ Comprehensive hooks system for enforcing spec-driven development at the Claude C
|
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
framework/hooks/
|
|
9
|
-
├── claude-code/
|
|
10
|
-
│ ├── core/
|
|
11
|
-
│
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
│ │ ├── block-destructive-worktree-removal.js # PreToolUse(Bash): raw removal follows junctions out
|
|
15
|
-
│ │ ├── block-raw-worktree-add.js # PreToolUse(Bash): raw `git worktree add` is ungoverned
|
|
16
|
-
│ │ └── warn-bash-source-write.js # PreToolUse(Bash): source written by redirect skips the Write/Edit guards
|
|
17
|
-
│ ├── user-prompt/
|
|
18
|
-
│ │ └── enrich-prompt.js # Context-aware prompt enrichment
|
|
19
|
-
│ ├── pre-tool-use/
|
|
20
|
-
│ │ ├── protect-spec-files.js # Block edits to approved spec artifacts
|
|
21
|
-
│ │ └── enforce-phase-writes.js # Enforce writes to correct phase dir
|
|
22
|
-
│ ├── post-tool-use/
|
|
23
|
-
│ │ └── dispatch.js # Dispatch on CLI commands (auto-checkpoint)
|
|
24
|
-
│ ├── stop/
|
|
25
|
-
│ │ └── validate-completion.js # Advisory: warn about incomplete work
|
|
26
|
-
│ ├── pre-compact/
|
|
27
|
-
│ │ └── save-morph-context.js # Snapshot state before compaction
|
|
28
|
-
├── shared/ # Reusable utilities for all hooks
|
|
29
|
-
│ ├── state-reader.js # Read-only state.json accessor
|
|
30
|
-
│ ├── phase-utils.js # Phase constants and path utilities
|
|
31
|
-
│ ├── hook-response.js # JSON response builders
|
|
32
|
-
│ └── stdin-reader.js # Stdin JSON reader
|
|
33
|
-
├── git/ # Git hooks (Bash)
|
|
34
|
-
│ ├── pre-commit/
|
|
35
|
-
│ │ ├── orchestrator.sh # Master hook dispatcher
|
|
36
|
-
│ │ └── agents.sh # Validates agents.json schema
|
|
37
|
-
│ ├── commit-msg/
|
|
38
|
-
│ │ └── conventional-commits.sh # Enforces conventional commits
|
|
39
|
-
│ └── pre-push/
|
|
40
|
-
│ └── run-tests.sh # Runs test suite before push
|
|
41
|
-
└── README.md # This file
|
|
9
|
+
├── claude-code/
|
|
10
|
+
│ ├── core/ # every hook, plus chain.js (one process per multi-hook group)
|
|
11
|
+
│ └── statusline.py # global statusline
|
|
12
|
+
├── shared/ # code hooks share (and src/ imports): state reader, guards, e2e, heavy, telemetry
|
|
13
|
+
└── README.md
|
|
42
14
|
```
|
|
43
15
|
|
|
16
|
+
The registered set is `MORPH_HOOKS` in `src/utils/hooks-installer.js` — the table below lists it. No hook builds,
|
|
17
|
+
typechecks or runs tests: that is `morph-spec verify`.
|
|
18
|
+
|
|
44
19
|
## Hook Events
|
|
45
20
|
|
|
46
21
|
| Event | Hook | Type | Purpose |
|
|
@@ -104,17 +79,6 @@ The installer writes to `.claude/settings.local.json`:
|
|
|
104
79
|
}
|
|
105
80
|
```
|
|
106
81
|
|
|
107
|
-
### Git Hooks
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
# In your project root
|
|
111
|
-
cd .git/hooks
|
|
112
|
-
ln -sf ../../framework/hooks/git/pre-commit/orchestrator.sh pre-commit
|
|
113
|
-
ln -sf ../../framework/hooks/git/commit-msg/conventional-commits.sh commit-msg
|
|
114
|
-
ln -sf ../../framework/hooks/git/pre-push/run-tests.sh pre-push
|
|
115
|
-
chmod +x pre-commit commit-msg pre-push
|
|
116
|
-
```
|
|
117
|
-
|
|
118
82
|
## Shared Utilities
|
|
119
83
|
|
|
120
84
|
All Claude Code hooks import from `framework/hooks/shared/`:
|
|
@@ -165,7 +165,7 @@ function handleAskUserQuestion(input, sessionRoot) {
|
|
|
165
165
|
|
|
166
166
|
// Same contract as `approve --self-assessed`: approving review without its reports is stamped, never silent.
|
|
167
167
|
const selfAssessed = gate === 'review' && feature?.workType !== 'hotfix'
|
|
168
|
-
&& ['verifier.md', 'reviewer.md'].some((f) => !existsSync(join(root, '.morph', 'features', featureName, '4-review', f)));
|
|
168
|
+
&& (feature?.workType === 'chore' ? ['reviewer.md'] : ['verifier.md', 'reviewer.md']).some((f) => !existsSync(join(root, '.morph', 'features', featureName, '4-review', f)));
|
|
169
169
|
if (selfAssessed) {
|
|
170
170
|
warnings.push(
|
|
171
171
|
`⚠️ [morph-spec] Gate 3 de "${featureName}" aprovado SEM os relatórios de review independentes (verifier.md, reviewer.md) — ` +
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getPendingGates,
|
|
11
11
|
} from '../../shared/state-reader.js';
|
|
12
12
|
import { sliceProgress } from '../../shared/slice-progress.js';
|
|
13
|
+
import { collectNextSignals, decideNext, formatNext } from '../../shared/next-step.js';
|
|
13
14
|
import { contextResult, runStandalone } from '../../shared/hook-response.js';
|
|
14
15
|
import { acquireClaim, foreignClaim, claimAge } from '../../shared/claim-registry.js';
|
|
15
16
|
import { rotateActivityLogIfLarge } from '../../shared/activity-logger.js';
|
|
@@ -133,11 +134,31 @@ function buildHandoffLine(dir, feature) {
|
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
// No git here, so verify staleness is unknown: an escalation the tree already moved past, or the
|
|
138
|
+
// root skeleton of a feature living in a worktree, would be an order read from stale facts.
|
|
139
|
+
function buildNextLine(dir, feature) {
|
|
140
|
+
try {
|
|
141
|
+
const signals = collectNextSignals(feature, dir);
|
|
142
|
+
if (!signals) return null;
|
|
143
|
+
if (signals.worktreePath && !samePath(resolve(signals.worktreePath), resolve(dir))) {
|
|
144
|
+
return `▶ ${feature} vive no worktree ${signals.worktreePath}: abra a sessão lá (aqui é o esqueleto de nascimento).`;
|
|
145
|
+
}
|
|
146
|
+
const next = decideNext(signals);
|
|
147
|
+
if (next.action === 'escalate') return `▶ verify vermelho de novo: rode \`morph-spec next ${feature}\` antes de agir.`;
|
|
148
|
+
return `▶ ${formatNext(next)} (confirme com \`morph-spec next ${feature}\` depois de mudar a árvore)`;
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
136
154
|
function buildWorktreeContext(dir, feature) {
|
|
137
155
|
const phase = getFeaturePhase(feature, dir);
|
|
138
156
|
let text = `🌳 Worktree desta feature: ${feature} (branch morph/${feature}, fase ${phase}). ` +
|
|
139
157
|
`Trabalhe apenas nesta feature nesta sessão; o estado vive no .morph/ deste worktree.`;
|
|
140
158
|
|
|
159
|
+
const nextLine = buildNextLine(dir, feature);
|
|
160
|
+
if (nextLine) text += `\n${nextLine}`;
|
|
161
|
+
|
|
141
162
|
const envLine = buildEnvLine(dir);
|
|
142
163
|
if (envLine) text += `\n${envLine}`;
|
|
143
164
|
|
|
@@ -173,6 +194,8 @@ function buildActiveFeatureSummary(dir) {
|
|
|
173
194
|
if (pending.length) {
|
|
174
195
|
text += ` · gates pendentes: ${pending.join(', ')}`;
|
|
175
196
|
}
|
|
197
|
+
const nextLine = buildNextLine(dir, active.name);
|
|
198
|
+
if (nextLine) text += `\n${nextLine}`;
|
|
176
199
|
return text;
|
|
177
200
|
}
|
|
178
201
|
|
|
@@ -390,13 +390,14 @@ def derive_phase(feature_dir, gates):
|
|
|
390
390
|
|
|
391
391
|
|
|
392
392
|
PARTIAL_MARKER = re.compile(r'^[\s#>*_]*PARCIAL\b')
|
|
393
|
+
BLOCKED_MARKER = re.compile(r'^[\s#>*_]*BLOQUEADO\b')
|
|
393
394
|
|
|
394
395
|
|
|
395
396
|
def count_slices(feature_dir):
|
|
396
397
|
"""(done, total) of the slices in 2-plan/checks.json.
|
|
397
398
|
|
|
398
399
|
Same rule as framework/hooks/shared/slice-progress.js: a slice is done when
|
|
399
|
-
3-implement/reports/{id}.md exists and its first line is
|
|
400
|
+
3-implement/reports/{id}.md exists and its first line is neither PARCIAL nor BLOQUEADO.
|
|
400
401
|
"""
|
|
401
402
|
try:
|
|
402
403
|
slices = json.loads((feature_dir / '2-plan' / 'checks.json').read_text(encoding='utf-8')).get('slices')
|
|
@@ -412,7 +413,7 @@ def count_slices(feature_dir):
|
|
|
412
413
|
except Exception:
|
|
413
414
|
continue
|
|
414
415
|
first = next((line for line in text.splitlines() if line.strip()), '')
|
|
415
|
-
if not PARTIAL_MARKER.match(first):
|
|
416
|
+
if not PARTIAL_MARKER.match(first) and not BLOCKED_MARKER.match(first):
|
|
416
417
|
done += 1
|
|
417
418
|
return done, len(ids)
|
|
418
419
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# morph-spec statusline — installed globally to ~/.claude/statusline.sh
|
|
3
|
-
# Claude Code invokes this with JSON via stdin after each response.
|
|
4
|
-
# Requires: Python 3 available as `python3` or `python` on PATH.
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
-
if command -v python3 &>/dev/null; then
|
|
8
|
-
python3 "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
9
|
-
elif command -v python &>/dev/null; then
|
|
10
|
-
python "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
11
|
-
fi
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# morph-spec statusline — installed globally to ~/.claude/statusline.sh
|
|
3
|
+
# Claude Code invokes this with JSON via stdin after each response.
|
|
4
|
+
# Requires: Python 3 available as `python3` or `python` on PATH.
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
if command -v python3 &>/dev/null; then
|
|
8
|
+
python3 "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
9
|
+
elif command -v python &>/dev/null; then
|
|
10
|
+
python "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
11
|
+
fi
|