@polymorphism-tech/morph-spec 8.24.3 → 8.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +2 -1
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/CLAUDE.md +2 -2
  7. package/framework/MORPH.md +22 -21
  8. package/framework/agents.json +1 -1
  9. package/framework/commands/create-umbraco-lp.md +1 -1
  10. package/framework/commands/migrate-lp-to-umbraco.md +1 -1
  11. package/framework/commands/morph-apply.md +4 -4
  12. package/framework/commands/morph-hotfix.md +3 -3
  13. package/framework/commands/morph-proposal.md +5 -5
  14. package/framework/docs/CLI.md +4 -4
  15. package/framework/evals/rubrics/condensed.md +2 -2
  16. package/framework/evals/rubrics/test-coverage.md +7 -0
  17. package/framework/hooks/claude-code/core/protect-spec-files.js +115 -4
  18. package/framework/hooks/shared/payload-utils.js +19 -4
  19. package/framework/rules/dotnet-templates.md +2 -1
  20. package/framework/rules/morph-workflow.md +42 -29
  21. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  22. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  23. package/framework/skills/core/morph-apply/SKILL.md +6 -6
  24. package/framework/skills/core/morph-brainstorming/SKILL.md +2 -2
  25. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  26. package/framework/skills/core/morph-hotfix/SKILL.md +7 -7
  27. package/framework/skills/core/morph-implement/SKILL.md +21 -6
  28. package/framework/skills/core/morph-plan/SKILL.md +15 -3
  29. package/framework/skills/core/morph-proposal/SKILL.md +7 -7
  30. package/framework/skills/core/morph-review/SKILL.md +10 -3
  31. package/framework/skills/core/morph-stack-scan/SKILL.md +3 -3
  32. package/framework/skills/core/morph-standards/SKILL.md +4 -4
  33. package/framework/skills/core/morph-uiux/SKILL.md +3 -3
  34. package/framework/templates/README.md +72 -643
  35. package/framework/templates/examples/spec-examples.md +2 -1
  36. package/package.json +1 -1
  37. package/src/commands/project/claim.js +19 -3
  38. package/src/commands/state/approve.js +17 -3
  39. package/src/commands/state/finish.js +202 -15
  40. package/src/commands/state/gate-check.js +9 -20
  41. package/src/commands/state/report-check.js +157 -5
  42. package/src/lib/evaluator-report.js +72 -0
  43. package/src/lib/risk-detector.js +35 -0
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:** 8.24.3
6
+ **Version:** 8.26.0
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -368,4 +368,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
368
368
 
369
369
  ---
370
370
 
371
- *morph-spec v8.24.3 by [Polymorphism Tech](https://polymorphism.tech)*
371
+ *morph-spec v8.26.0 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -232,7 +232,7 @@ program
232
232
  .option('--name <name>', 'Human-readable feature name (default: the slug)')
233
233
  .option('--description <text>', 'What/why of the feature — required at birth unless --request is given (it seeds the description)')
234
234
  .option('--status <status>', 'Initial feature status (default: in_progress)')
235
- .option('--type <type>', 'Work type: feature|bug|chore|hotfix (default: feature) — see src/core/work-types.js')
235
+ .option('--type <type>', 'Work type: feature|bug|chore|hotfix (default: feature) — each type stamps its own gate policy')
236
236
  .option('--request <text>', 'Free-text request classified by the deterministic detector; tie/none exits 2 with candidates for an LLM/user decision (feature NOT created)')
237
237
  .option('--detect-source <source>', 'workTypeDetection.source recorded when --type is explicit: deterministic|llm|user (default: user)')
238
238
  .option('--worktree', 'Create the feature already inside its own git worktree (branch morph/{feature}) — fails open outside a git repo')
@@ -442,6 +442,7 @@ program
442
442
  .description('claim [feature] acquires it (moves this tree\'s previous claim in the same step); no args shows the board (claims are advisory warnings, never locks)')
443
443
  .option('--release', 'Release the claim on <feature> (recovery: the owning session died)')
444
444
  .option('--list', 'Read-only: show claims without acquiring (default when no feature is given)')
445
+ .option('--all', 'Include dead claims (past the 30min TTL) — hidden by default, they no longer warn about anything')
445
446
  .option('--json', 'Output as JSON')
446
447
  .action((feature, options) => claimCommand(feature, options));
447
448
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "8.24.3",
3
+ "version": "8.26.0",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",
@@ -185,4 +185,4 @@ Never edited directly — go through CLI commands or `morph-spec update` instead
185
185
 
186
186
  ---
187
187
 
188
- *morph-spec v8.24.3 by Polymorphism Tech*
188
+ *morph-spec v8.26.0 by Polymorphism Tech*
@@ -249,4 +249,4 @@ morph-spec doctor
249
249
 
250
250
  ---
251
251
 
252
- *morph-spec v8.24.3 by Polymorphism Tech*
252
+ *morph-spec v8.26.0 by Polymorphism Tech*
@@ -15,9 +15,9 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
15
15
 
16
16
  **NUNCA:**
17
17
  - Pular para código sem proposal aprovado (Gate 1) e plano aprovado (Gate 2).
18
- - Modificar o contrato do plano depois do Gate 2 — `spec.md`, `mandate.md`, e os campos `title`/`description`/`dependencies`/`effort`/`doneCriteria`/`group`/`agent`/`standards` de `tasks.json` ficam congelados (`protect-spec-files` bloqueia via `Write` ou `Edit`). Campos mutáveis `status`/`outputs`/`notes` continuam graváveis (ver SEMPRE). **Use cases (`2-plan/usecases/UC-*.md`) NÃO estão congelados** — editáveis por design, é assim que se muda o comportamento e se re-roda `implement`.
18
+ - Modificar o contrato do plano depois do Gate 2 — `spec.md`, `mandate.md`, e os campos `title`/`description`/`dependencies`/`effort`/`doneCriteria`/`group`/`agent`/`standards` de `tasks.json` ficam congelados (`protect-spec-files` bloqueia `Write`/`Edit`); mutáveis: `status`/`outputs`/`notes` (ver SEMPRE). **Fato errado sobre o código não exige revogar gate**: bloco `## Errata` append-only no fim, corpo intacto (MORPH.md §3). **Use cases (`2-plan/usecases/UC-*.md`) NÃO congelam** — editáveis por design.
19
19
  - Escrever critérios de aceite no `proposal.md` — pertencem aos use cases (pós-condição **observável em teste**, chega **literal** ao `doneCriteria`).
20
- - Editar `.morph/specs/usecases/` à mão — só o `finish` escreve lá (lê de `2-plan/usecases/`, carimba `status: implemented`, sobrescreve por ID). Mudar um UC existente (caso `bug`): copie para `2-plan/usecases/` da feature e corrija lá.
20
+ - Editar `.morph/specs/usecases/` à mão — só o `finish` escreve lá (lê de `2-plan/usecases/`, carimba `status: implemented`, sobrescreve por ID). Mudar um UC existente (`bug`): copie para `2-plan/usecases/` da feature e corrija lá.
21
21
  - Listar perguntas em texto simples — sempre `AskUserQuestion` (1-4 perguntas por chamada).
22
22
  - Usar `Guid.NewGuid()`, classes não-sealed sem justificativa, exceptions como fluxo de controle, camada de Service/Manager entre handler e dados, repository genérico (`IRepository<T>`/`IUnitOfWork`), ou SQL cru sem justificativa.
23
23
  - Editar `taskScores`, `gateDecisions` ou gates de `feature.json` à mão — sempre via `morph-spec score`/`gate-check`/`approve` (timestamps server-side; Edit manual colide com o hook `state-sync`).
@@ -150,10 +150,12 @@ Não invente caminhos alternativos.
150
150
 
151
151
  - `mandate.md` — contrato de implementação: stack exata, standards obrigatórios (com links), patterns proibidos, **verificação de runtime** (como o stack sobe, que fluxos são exercitados, quais viram spec Playwright, setup de auth) e checklist de done verificável. Lido no início de cada task — específico o bastante para resolver ambiguidades sem consultar o Orchestrator.
152
152
 
153
- - `tasks.json` — validada contra `framework/schemas/tasks.schema.json`. Cada task: `id` (T1, T2...), `title`, `description`, `dependencies` (IDs), `effort` (S/M/L/XL), `doneCriteria`, `status` (pending/in_progress/done/skipped/blocked), `outputs` (paths esperados), `usecase` (opcional — **ID** do use case, `UC-07`, nunca o nome do arquivo). `doneCriteria` de task com `usecase` carrega a **pós-condição literal**, com rótulo (`"P2 — cada veterinário traz..."`). Toda entrada do recap.md referencia um ID do tasks.json.
153
+ - `tasks.json` — validada contra `.morph/framework/schemas/tasks.schema.json`. Cada task: `id` (T1, T2...), `title`, `description`, `dependencies` (IDs), `effort` (S/M/L/XL), `doneCriteria`, `status` (pending/in_progress/done/skipped/blocked), `outputs` (paths esperados), `usecase` (opcional — **ID** do use case, `UC-07`, nunca o nome do arquivo). `doneCriteria` de task com `usecase` carrega a **pós-condição literal**, com rótulo (`"P2 — cada veterinário traz..."`). Toda entrada do recap.md referencia um ID do tasks.json.
154
154
 
155
155
  - `decisions.md` — log de decisões técnicas (alternativa descartada, motivo, quem decidiu). Só existe se houver decisão não trivial a rastrear.
156
156
 
157
+ **Errata em spec/mandate congelados:** congelar o CONTRATO não é congelar um ERRO DE FATO. Se a spec afirma sobre o código algo que o código desmente, acrescente um bloco `## Errata` no FIM do arquivo — corpo intacto, append-only, aceito pelo `protect-spec-files` sem revogar gate. Mudança de ESCOPO continua exigindo revogar o gate e re-aprovar.
158
+
157
159
  ### Implement (`3-implement/`)
158
160
 
159
161
  - `recap.md` — atualizado ao fim de cada task: ID, o que foi feito, arquivos modificados, problemas e resolução, standard consultado. Categoria sem nada a registrar → escreva "nenhum" (recap vazio é pior que mínimo).
@@ -184,7 +186,7 @@ Cada fase produz um trace JSON em `.morph/traces/YYYY-MM-DD-{feature}-{phase}.js
184
186
 
185
187
  **Nunca carregue todos os standards de uma vez** — custa tokens sem benefício, você não sabe quais serão relevantes antes de começar. Consulte sob demanda, por tarefa.
186
188
 
187
- Índice em `framework/standards/STANDARDS.json` (`id`, `name`, `path`, `category`, `tags`).
189
+ Índice em `.morph/framework/standards/STANDARDS.json` (`id`, `name`, `path`, `category`, `tags`).
188
190
 
189
191
  **Fluxo de consulta:**
190
192
 
@@ -241,7 +243,7 @@ Para alterar o modo: editar `.morph/config/config.json → trust` (`"auto"` ou `
241
243
 
242
244
  **Registre quem aprovou (`approvedBy`):** objeto `{ approved, timestamp, approvedBy }`. `"auto"` — política `trust: auto` sem decisão humana. `"manual"` — humano aprovou explicitamente (via `AskUserQuestion`, ou, sem ela em headless, via texto começando em "aprovado"); em `trust: manual` todo gate é `"manual"` — nunca grave `"auto"` num gate que um humano aprovou. Mantém o audit trail honesto nos contextos headless (CI, `claude -p`) onde mais importa.
243
245
 
244
- **O hook gate-guard.js** confere, sem bloquear, se a ação tomada num gate (pausar vs auto-aprovar) bateu com a decisão persistida. **Default:** `gate-check <feature> <gate> [--signals <json>] [--json]` decide via `shouldPauseGate`; `--signals` sobrescreve só o que o disco não expõe sozinho (`designSystemDivergence`, `standardsViolations`, `evalScore`). **Escape manual** (sinal que `shouldPauseGate` nem representa, ex.: modo de descoberta): `gate-decision <feature> <gate> --pause <bool> [--reason <txt>] [--signals <json>]`. Ambos gravam `gateDecisions[gate] = { pause, reason, signals, trust, decidedAt }` server-side (shape em `framework/schemas/feature.schema.json`). **Nunca edite `gateDecisions` à mão** — colide com state-sync. Divergência → o guard injeta aviso no contexto; cada conferência registra `coherent`/`divergent` em `.morph/logs/activity.json` — nunca bloqueia.
246
+ **O hook gate-guard.js** confere, sem bloquear, se a ação tomada num gate (pausar vs auto-aprovar) bateu com a decisão persistida. **Default:** `gate-check <feature> <gate> [--signals <json>] [--json]` decide via `shouldPauseGate`; `--signals` sobrescreve só o que o disco não expõe sozinho (`designSystemDivergence`, `standardsViolations`, `evalScore`). **Escape manual** (sinal que `shouldPauseGate` nem representa, ex.: modo de descoberta): `gate-decision <feature> <gate> --pause <bool> [--reason <txt>] [--signals <json>]`. Ambos gravam `gateDecisions[gate] = { pause, reason, signals, trust, decidedAt }` server-side (shape em `.morph/framework/schemas/feature.schema.json`). **Nunca edite `gateDecisions` à mão** — colide com state-sync. Divergência → o guard injeta aviso no contexto; cada conferência registra `coherent`/`divergent` em `.morph/logs/activity.json` — nunca bloqueia.
245
247
 
246
248
  ---
247
249
 
@@ -268,10 +270,9 @@ Inline (sem dispatch) é aceitável para tasks curtas (S/M) **dentro do domínio
268
270
 
269
271
  **NÃO passe:** histórico da conversa principal, contexto de outras tarefas não relacionadas, outputs de ferramentas não pertinentes.
270
272
 
271
- **O ARQUIVO é o retorno, a última mensagem é cortesia.** Em uso real, o evento recebido de volta do
272
- sub-agent às vezes é só um `idle_notification` sem texto — a última mensagem **pode não chegar**
273
- inteira ao orquestrador, por isso ela deixou de ser o canal autoritativo. O sub-agent **sempre**
274
- grava o relatório também em um arquivo canônico:
273
+ **O ARQUIVO é o retorno, a última mensagem é cortesia.** O evento de volta do sub-agent às vezes é só
274
+ um `idle_notification` sem texto — a última mensagem **pode não chegar** inteira, por isso não é o
275
+ canal autoritativo. O sub-agent **sempre** grava o relatório num arquivo canônico:
275
276
 
276
277
  - Task de implementação: `.morph/features/{feature}/3-implement/reports/{taskId}.md` (um arquivo por task concluída, mesmo quando uma sessão de sub-agent cobre várias tasks de um fluxo).
277
278
  - Avaliador do Gate 3 (`morph-evaluator`): `.morph/features/{feature}/4-review/evaluator-report.md` — relatório BRUTO; a `morph-review` o lê e sintetiza o `review-report.md` do Gate 3 (nunca colidem: um é saída crua do sub-agent, o outro é curadoria do orquestrador).
@@ -279,14 +280,14 @@ grava o relatório também em um arquivo canônico:
279
280
  O orquestrador **lê o arquivo** ao retomar controle após o dispatch — nunca confia só na mensagem
280
281
  de retorno da tool (que, se chegou inteira, só economiza a leitura).
281
282
 
282
- ### Protocolo de ausência de relatório
283
+ ### Protocolo de auditoria do relatório
283
284
 
284
- **"Relatório ausente = o agente falhou" é a leitura errada.** O sub-agent pode ter feito todo o
285
- trabalho e perdido o meio de gravá-lo (ex.: o worktree sumiu debaixo dele em execução). Tratar isso
286
- como falha joga fora trabalho real.
285
+ **"Relatório ausente = o agente falhou" é a leitura errada** — ele pode ter feito o trabalho e
286
+ perdido o meio de gravá-lo (ex.: o worktree sumiu debaixo dele). E relatório PRESENTE também não
287
+ basta: um gravado cedo demais fica completo e desatualizado.
287
288
 
288
- A pergunta que decide não é "o relatório chegou?", é **"o TRABALHO entrou?"** — respondida por
289
- evidência, não pela palavra do agente:
289
+ A pergunta que decide não é "o relatório chegou?", é **"o TRABALHO entrou, e é isto que ele diz?"** —
290
+ respondida por evidência, não pela palavra do agente:
290
291
 
291
292
  ```
292
293
  morph-spec report-check <feature> [task] --json
@@ -294,15 +295,15 @@ morph-spec report-check <feature> [task] --json
294
295
 
295
296
  | veredito | o que significa | ação |
296
297
  | --- | --- | --- |
297
- | `ok` | relatório presente e com os 4 itens | siga o fluxo |
298
+ | `ok` | relatório presente, com os 4 itens, e mais novo que o trabalho | siga o fluxo |
299
+ | `report-stale` | relatório completo mas **VELHO**: a task escreveu arquivos DEPOIS de ele ser gravado (o agente relatou cedo e continuou; ex.: "teste ainda não escrito" com os testes já no disco) | **não re-despache** — leia `observed` (writes reais do `events.jsonl`, não `outputs` auto-declarado) e atualize o relatório a partir dessa evidência |
298
300
  | `report-incomplete` | relatório existe, faltam itens (ele lista quais) | complete a partir da mensagem de retorno, carimbe **DEGRADADO** |
299
301
  | `work-landed-report-missing` | sem relatório, mas os `outputs` da task estão no disco | **não re-despache** — grave você mesmo o relatório canônico a partir da mensagem de retorno, carimbado **DEGRADADO** |
300
302
  | `no-work-no-report` | sem relatório e sem output | re-despache **uma** vez com o mesmo prompt |
301
303
  | `report-missing-evidence-unknown` | sem relatório e a task não declarou `outputs` | re-despache (sem evidência, o conservador é o certo) |
302
304
 
303
- Sai 0 só em `ok`. Em qualquer caminho degradado a degradação é **registrada** — no recap e nas
304
- `notes` da task —, nunca aceita em silêncio. Segunda ausência sem trabalho entregue é falha real:
305
- escale ao humano.
305
+ Sai 0 só em `ok`. Todo caminho degradado é **registrado** no recap e nas `notes` da task, nunca
306
+ aceito em silêncio. Segunda ausência sem trabalho entregue é falha real: escale ao humano.
306
307
 
307
308
  **O que receber de volta** (no arquivo, e na mensagem final quando ela chega) — os **4 itens obrigatórios** de todo relatório de dispatch:
308
309
  - O que foi feito (2-3 frases)
@@ -325,16 +326,16 @@ Sub-agent deve ser autossuficiente com o que você passou — se precisar de mai
325
326
 
326
327
  **Dispatch em paralelo (multi-agent):** fluxos **genuinamente independentes** (ex: backend ‖ frontend, convergindo só no fim) → despache múltiplos sub-agents **ao mesmo tempo**, um por fluxo, em vez de série. `morph-spec dag {feature}` analisa o DAG (`dependencies`/`group`), **pondera o esforço** (raízes/fluxos só de tasks S não ganham dispatch próprio) e imprime a decisão; regras e cap de agentes simultâneos em `morph-apply` ("Análise de DAG"). Colisão de `outputs` entre fluxos → `dag` recomenda `isolation: "worktree"`, cada agente num worktree próprio, orquestrador integra por merge (`morph-apply` §3b-w). Sem colisão: working tree compartilhada, verificação única pós-batch.
327
328
 
328
- O padrão de dispatch e o template de prompt para sub-agents estão detalhados em `framework/skills/core/morph-implement/SKILL.md`.
329
+ O padrão de dispatch e o template de prompt para sub-agents estão detalhados em `morph-implement` §4.
329
330
 
330
331
  ---
331
332
 
332
333
  ## 7. Skills disponíveis
333
334
 
334
- Skills são documentos de instrução que você invoca sob demanda. Eles ficam em `framework/skills/core/`.
335
+ Skills são documentos de instrução que você invoca sob demanda. Eles ficam em `.claude/skills/`.
335
336
  Não invoque uma skill fora do momento indicado.
336
337
 
337
- Path de cada skill: `framework/skills/core/{nome}/SKILL.md`.
338
+ Path de cada skill: `.claude/skills/{nome}/SKILL.md`.
338
339
 
339
340
  | Skill | Quando invocar |
340
341
  | ----------------------- | --------------------------------------------------------------------------- |
@@ -352,7 +353,7 @@ Path de cada skill: `framework/skills/core/{nome}/SKILL.md`.
352
353
 
353
354
  Slash commands (`/morph-proposal`, `/morph-apply`, `/morph-hotfix` — linhas acima) são atalhos que invocam a `Skill()` correspondente; o agente pode chamá-la direto quando o usuário pede "use /morph-proposal".
354
355
 
355
- Cada skill contém instruções passo a passo — leia, siga, não desvie sem justificativa no recap.md. Só `framework/skills/core/` é instalado. Nenhuma skill é invocada proativamente por hook ou script — você decide quando a situação pede o rigor dela.
356
+ Cada skill contém instruções passo a passo — leia, siga, não desvie sem justificativa no recap.md. Só as skills **core** do framework são instaladas. Nenhuma skill é invocada proativamente por hook ou script — você decide quando a situação pede o rigor dela.
356
357
 
357
358
  ---
358
359
 
@@ -147,7 +147,7 @@
147
147
  "tools": "Read, Glob, Grep, Bash",
148
148
  "mcp": ["playwright"],
149
149
  "model": "opus",
150
- "persona": "Você aplica as rubricas em .morph/framework/evals/rubrics/. Você não participou da implementação — avalia o mandate, o diff e a saída real dos testes, com contexto fresco. O orquestrador anexa o resultado do `morph-spec verify`, mas você tem Bash para conferir por conta própria: rode `morph-spec verify {feature} [task]`, testes/build do stack, `git log/diff/show` — nunca confie cegamente no que foi reportado. Bash é só para verificação: NUNCA para modificar código-fonte, tasks.json, feature.json ou o board de sessão — quem faz isso é o loop de correção, não você. Você também tem Playwright: quando o nó `e2e` do verify subiu o stack, NAVEGUE a aplicação como um usuário faria e percorra os fluxos da feature — ler o diff prova que o código existe, usar a aplicação prova que ela funciona. Confira console e network atrás de erro que nenhum teste cobre. Quem sobe e derruba o stack é o `morph-spec verify`/`morph-spec e2e`, não você: se ele não subiu, isso é um achado de infra a reportar, não um defeito de implementação a pontuar. Se um fluxo que você exercitou merece virar regressão, recomende a spec Playwright no relatório — recomendar é seu papel, escrever não. Não reescreve código — produz scores 0-10 por dimensão (architecture, contracts, code-quality, test-coverage), feedback priorizado acionável, e ação clara (aprovado em >=9 / feedback para o loop de correção em <9). Silêncio em scores ≥ 9 é o sinal correto."
150
+ "persona": "Você aplica as rubricas em .morph/framework/evals/rubrics/. Você não participou da implementação — avalia o mandate, o diff e a saída real dos testes, com contexto fresco. O orquestrador anexa o resultado do `morph-spec verify`, mas você tem Bash para conferir por conta própria: rode `morph-spec verify {feature} [task]`, testes/build do stack, `git log/diff/show` — nunca confie cegamente no que foi reportado. Bash é só para verificação: NUNCA para modificar código-fonte, tasks.json, feature.json ou o board de sessão — quem faz isso é o loop de correção, não você. Você também tem Playwright: quando o nó `e2e` do verify subiu o stack, NAVEGUE a aplicação como um usuário faria e percorra os fluxos da feature — ler o diff prova que o código existe, usar a aplicação prova que ela funciona. Confira console e network atrás de erro que nenhum teste cobre. Quem sobe e derruba o stack é o `morph-spec verify`/`morph-spec e2e`, não você: se ele não subiu, isso é um achado de infra a reportar, não um defeito de implementação a pontuar. Se um fluxo que você exercitou merece virar regressão, recomende a spec Playwright no relatório — recomendar é seu papel, escrever não. Quando o que você avalia é uma CORREÇÃO (rodada do loop, fix de achado seu ou de outrem), pergunte sempre O QUE ESSA CORREÇÃO ABRIU: que comportamento antes correto pode ter regredido, e se há teste que pegaria isso. Um fix que fecha um defeito e abre outro pontua alto em toda dimensão e mesmo assim piorou o produto — e re-execute os seus próprios probes da rodada anterior em vez de aceitar 'agora tem teste' como fechamento. Não reescreve código — produz scores 0-10 por dimensão (architecture, contracts, code-quality, test-coverage), feedback priorizado acionável, e ação clara (aprovado em >=9 / feedback para o loop de correção em <9). Silêncio em scores ≥ 9 é o sinal correto."
151
151
  },
152
152
  "scout": {
153
153
  "title": "Codebase Scout",
@@ -29,7 +29,7 @@ Cria um projeto de landing page novo a partir do template canônico `MORPH_Umbra
29
29
 
30
30
  ## Referências
31
31
 
32
- - `framework/skills/core/create-umbraco-lp/SKILL.md`
32
+ - `.claude/skills/create-umbraco-lp/SKILL.md`
33
33
  - `docs/umbraco-lp-patterns.md` (em `main`)
34
34
 
35
35
  **Projeto:** $ARGUMENTS
@@ -26,7 +26,7 @@ Migra a LP de origem para o template `MORPH_Umbraco_LP_Template` de forma reprod
26
26
 
27
27
  ## Referências
28
28
 
29
- - `framework/skills/core/migrate-lp-to-umbraco/SKILL.md` — playbook completo (passos 0-6, gate de paridade)
29
+ - `.claude/skills/migrate-lp-to-umbraco/SKILL.md` — playbook completo (passos 0-6, gate de paridade)
30
30
  - `polymorphism-tech/MORPH_Umbraco_LP_Template` — template canônico (privado, branch `master`), obtido no passo 0
31
31
  - `MORPH_Umbraco_LP_Template/scripts/new-lp.sh` / `new-lp.ps1` — scaffold
32
32
 
@@ -31,9 +31,9 @@ Se algum estiver faltando, abortar e pedir ao usuário para rodar `/morph-propos
31
31
 
32
32
  ## Referências
33
33
 
34
- - `framework/MORPH.md` — charter completo
35
- - `framework/skills/core/morph-implement/SKILL.md`
36
- - `framework/skills/core/morph-eval/SKILL.md`
37
- - `framework/skills/core/morph-review/SKILL.md`
34
+ - `.morph/framework/MORPH.md` — charter completo
35
+ - `.claude/skills/morph-implement/SKILL.md`
36
+ - `.claude/skills/morph-eval/SKILL.md`
37
+ - `.claude/skills/morph-review/SKILL.md`
38
38
 
39
39
  **Feature:** $ARGUMENTS
@@ -32,8 +32,8 @@ Resolve uma queda ou bug crítico de produção com prioridade máxima em veloci
32
32
 
33
33
  ## Referências
34
34
 
35
- - `framework/skills/core/morph-hotfix/SKILL.md` — fluxo completo passo a passo
36
- - `framework/MORPH.md` §2.1 — roteamento por tipo de trabalho
37
- - `framework/MORPH.md` §5 — Gates de aprovação, gate humano forçado no hotfix
35
+ - `.claude/skills/morph-hotfix/SKILL.md` — fluxo completo passo a passo
36
+ - `.morph/framework/MORPH.md` §2.1 — roteamento por tipo de trabalho
37
+ - `.morph/framework/MORPH.md` §5 — Gates de aprovação, gate humano forçado no hotfix
38
38
 
39
39
  **Feature:** $ARGUMENTS
@@ -20,7 +20,7 @@ Conduz a feature do request inicial até o Gate 2 (plano aprovado), sem interven
20
20
  - Se existe e tem `0-proposal/proposal.md`: pular para passo 3.
21
21
  - Se não existe: criar a estrutura e seguir o passo 2.
22
22
 
23
- 2. **Invocar `morph-brainstorming`** (skill em `framework/skills/core/morph-brainstorming/`):
23
+ 2. **Invocar `morph-brainstorming`** (skill em `.claude/skills/morph-brainstorming/`):
24
24
  - Varre o ambiente **antes** de qualquer pergunta (stack, contexto do projeto, acervo de use cases).
25
25
  - Monta a árvore de decisões abertas — só o que a varredura não respondeu.
26
26
  - Abre com a escolha de **modo de descoberta** (entrevista ou automático), recomendando um.
@@ -31,7 +31,7 @@ Conduz a feature do request inicial até o Gate 2 (plano aprovado), sem interven
31
31
  - Resposta válida: "aprovado", "pode avançar", "lgtm".
32
32
  - Se ajustes solicitados: aplicar e reapresentar o gate.
33
33
 
34
- 4. **Após Gate 1, invocar `morph-plan`** (skill em `framework/skills/core/morph-plan/`):
34
+ 4. **Após Gate 1, invocar `morph-plan`** (skill em `.claude/skills/morph-plan/`):
35
35
  - Lê `proposal.md`.
36
36
  - Produz `spec.md`, `mandate.md`, `tasks.json` em `.morph/features/{feature-name}/2-plan/`.
37
37
  - Apresenta o **Gate 2** com template fixo (ver SKILL.md passo 5).
@@ -50,8 +50,8 @@ Conduz a feature do request inicial até o Gate 2 (plano aprovado), sem interven
50
50
 
51
51
  ## Referências
52
52
 
53
- - `framework/MORPH.md` — charter completo
54
- - `framework/skills/core/morph-brainstorming/SKILL.md`
55
- - `framework/skills/core/morph-plan/SKILL.md`
53
+ - `.morph/framework/MORPH.md` — charter completo
54
+ - `.claude/skills/morph-brainstorming/SKILL.md`
55
+ - `.claude/skills/morph-plan/SKILL.md`
56
56
 
57
57
  **Feature:** $ARGUMENTS
@@ -9,7 +9,7 @@
9
9
  - **`morph-spec approve <feature> <gate> --mode auto`** — aprova um gate (`proposal`/`uiux`/`plan`/`review`) em `trust: auto`. **`uiux`** é o único gate cujo `AskUserQuestion` o hook `state-sync.js` não reconhece por texto (só casa "Gate 1/2/3") — aprove-o sempre explicitamente por este comando ou por `advance`, nunca contando com o hook. **O Gate 3 exige `4-review/evaluator-report.md`**: sem o relatório do avaliador independente o comando recusa (exit 1). Se o dispatch do avaliador for impossível nesta sessão, aprove com `--self-assessed` — a degradação é gravada em `approvalGates.review.selfAssessed` e aparece marcada no `morph-spec list`. `--force` **não** dispensa esta regra (é só para ordem de gate). **Exceção `hotfix`:** esse tipo nunca despacha o avaliador (`morph-hotfix` §7); o Gate 3 exige `4-review/review-report.md` no lugar do `evaluator-report.md`, e a assinatura humana continua obrigatória via `--approver` (o backstop de `--mode auto` acima permanece recusando).
10
10
  - **`morph-spec advance <feature>`** — aprova o gate da fase corrente **e** cria a pasta da próxima fase num passo só (`--design` toma o ramo opcional de uiux a partir da proposta).
11
11
  - **`morph-spec score <feature> <task> <score> [--dims a,c,q,t]`** — grava `taskScores` no feature.json com timestamp server-side; **nunca edite taskScores à mão**. Em PowerShell, cite o valor de `--dims` entre aspas (`--dims "9.5,9,9,9"`) — sem aspas, a lista pode chegar como tokens separados; o parser aceita ambas as formas, mas citar evita qualquer ambiguidade.
12
- - **`morph-spec gate-check <feature> <gate> [--signals <json>] [--trust <mode>] [--json]`** — o caminho **default**: coleta do disco os sinais que `shouldPauseGate` (risk-detector.js) já sabe avaliar (`workType`, `taskCount`, verificação, sha/diffstat), decide a pausa e persiste `gateDecisions.{gate}`. `--signals` aceita só as chaves que o disco genuinamente não expõe sozinho — `designSystemDivergence`, `standardsViolations`, `evalScore` — qualquer outra chave é ignorada com aviso. No **review**, o `evalScore` real vem do token `**Composite-Score:** {n.n}` do `evaluator-report.md` (o avaliador o grava por contrato); sem o token, cai no proxy (média das `taskScores`); `--signals` vence ambos. Um stamp de `verification` com status `fail` (último `morph-spec verify` vermelho) **força pausa** em qualquer gate. Um stamp `pass` mas **stale** (sha diferente do HEAD atual — código mudou depois do último verify) força pausa **só no ship** (`review`); em `plan`/`uiux`/`proposal` 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).
12
+ - **`morph-spec gate-check <feature> <gate> [--signals <json>] [--trust <mode>] [--json]`** — o caminho **default**: coleta do disco os sinais que `shouldPauseGate` (risk-detector.js) já sabe avaliar (`workType`, `taskCount`, verificação, sha/diffstat), decide a pausa e persiste `gateDecisions.{gate}`. `--signals` aceita só as chaves que o disco genuinamente não expõe sozinho — `designSystemDivergence`, `standardsViolations`, `evalScore` — qualquer outra chave é ignorada com aviso. No **review**, o `evalScore` real vem do token `**Composite-Score:** {n.n}` do `evaluator-report.md` (o avaliador o grava por contrato). **Score que não venha desse token PAUSA o Gate 3**, qualquer que seja o valor: o proxy (média das `taskScores`) e o override de `--signals` medem auto-avaliação de quem implementou, não qualidade da feature — em campo, 7 tasks com auto-score 9.0-9.3 auto-passaram um produto que o avaliador independente pontuou 7.2 e reprovou com 3 P1. `evalScoreSource` registra a procedência (`evaluator-report` / `taskScores-proxy` / `signals-override`). Para forçar a passagem mesmo assim, use `gate-decision --pause false` — explícito e registrado. Fora do review a regra não vale: antes de haver implementação não existe avaliador independente. Um stamp de `verification` com status `fail` (último `morph-spec verify` vermelho) **força pausa** em qualquer gate. Um stamp `pass` mas **stale** (sha diferente do HEAD atual — código mudou depois do último verify) força pausa **só no ship** (`review`); em `plan`/`uiux`/`proposal` 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).
13
13
  - **`morph-spec gate-decision <feature> <gate> --pause <bool> [--reason <txt>] [--signals <json>]`** — o **escape manual**: registra uma decisão já tomada pela sessão (ex.: `discoveryMode: "interview"`, sinal que `shouldPauseGate` nem representa). Mesmo write path do `gate-check` — grava `gateDecisions` no feature.json com timestamp server-side (shape em `framework/schemas/feature.schema.json`); **nunca edite gateDecisions à mão** — o Edit manual colide com o hook state-sync.
14
14
 
15
15
  ## Features
@@ -21,14 +21,14 @@
21
21
 
22
22
  ## Worktree / Fleet
23
23
 
24
- - **`morph-spec finish <feature> --pr|--merge`** — o **encerramento único** da feature (worktree ou comum): **arquiva** commitando `features/{f} → archive/{f}` dentro de `morph/{f}` — promovendo junto, no mesmo commit, os use cases da feature para o acervo permanente `.morph/specs/usecases/` com `status: implemented` (fail-open: sem `2-plan/usecases/`, no-op silencioso) — depois integra. Um `UC-{nn}` cuja promoção colide com outra feature já publicada sob o mesmo número (`ucCollisions` no output; ver `usecase reserve` abaixo) **não é promovido**, mas isso nunca recusa o `finish` — só aquele use case fica de fora, com a mensagem apontando onde o arquivo ficou e como recuperar. `--merge` faz merge `--no-ff` **local** na default (sem push); `--pr` faz `push` + `gh pr create` (exige `origin` + `gh`, checados **antes** de arquivar — um `--pr` recusado não deixa meio-estado). Desmonta o worktree (PROVE-or-DECLINE) e libera o bloco de portas; no `--pr` **preserva** a branch (o PR precisa dela) e fica em `morph/{f}`. Flags: `--keep` mantém o worktree, `--force` dispensa o Gate 3, `--discard-ignored` consente descartar gitignored no teardown. Substitui o antigo `worktree finish` + `archive` separados.
24
+ - **`morph-spec finish <feature> --pr|--merge`** — o **encerramento único** da feature (worktree ou comum): **arquiva** commitando `features/{f} → archive/{f}` dentro de `morph/{f}` — promovendo junto, no mesmo commit, os use cases da feature para o acervo permanente `.morph/specs/usecases/` com `status: implemented` (fail-open: sem `2-plan/usecases/`, no-op silencioso) — depois integra. Um `UC-{nn}` cuja promoção colide com outra feature já publicada sob o mesmo número (`ucCollisions` no output; ver `usecase reserve` abaixo) **não é promovido**, mas isso nunca recusa o `finish` — só aquele use case fica de fora, com a mensagem apontando onde o arquivo ficou e como recuperar. `--merge` faz merge `--no-ff` **local** na default (sem push); `--pr` faz `push` + `gh pr create` (exige `origin` + `gh`, checados **antes** de arquivar — um `--pr` recusado não deixa meio-estado). Desmonta o worktree (PROVE-or-DECLINE) e libera o bloco de portas; no `--pr` **preserva** a branch (o PR precisa dela) e fica em `morph/{f}`. Flags: `--keep` mantém o worktree, `--force` dispensa o Gate 3, `--discard-ignored` consente descartar gitignored no teardown. Substitui o antigo `worktree finish` + `archive` separados. **Retomável:** conflito de merge, checkout ou push falham *depois* do archive (é o commit do archive que viaja com a branch) — re-executar o `finish` retoma na integração e sai com `resumed: true`, em vez de recusar por feature ausente. Se a integração já tiver acontecido, recusa com `already-finished` — e o que conta como "já integrada" depende do modo: no `--merge`, branch contida na default; no `--pr`, PR aberto para `morph/{f}` (a branch de um `--pr` concluído fica não-mergeada de propósito, então só o teste de ancestralidade deixaria passar um segundo `gh pr create`). Sem conseguir provar nem uma coisa nem outra, recusa com `resume-undecidable` em vez de retomar às cegas.
25
25
  - **`morph-spec usecase reserve [--count <n>] [--feature <name>] [--json]`** — reserva atomicamente os próximos N números `UC-{nn}`, compartilhado por **todo worktree** deste repo (`<git-common-dir>/morph-spec/uc-registry.json`, mesmo mecanismo do `claim`/env-registry do fleet). Substitui o scan manual "leia o acervo + as features da árvore e some 1": um scan só enxerga a ÁRVORE CORRENTE, então dois worktrees planejando ao mesmo tempo mintavam o MESMO número — colisão que a promoção do `finish` resolvia sobrescrevendo por ID, apagando o use case do primeiro a chegar (já custou perda de dados em produção). O contador nunca regride (semeado do maior número já visto no acervo + nas features da árvore + no próprio registro). `--feature` é mais que auditoria: é o sinal que o guarda de colisão do `finish`/`archive` usa para distinguir uma correção legítima (`bug`, nunca reserva — reaproveita um número publicado) de uma colisão real (a feature reservou o número e o acervo já tem outra dona nele); sem `--feature`, infere a única feature ativa se for inequívoco. Fora de um repositório git: cai para o scan local (avisa no output, `shared: false`) — ainda evita autocolisão dentro da mesma árvore, mas sem a garantia entre worktrees.
26
26
  - **`morph-spec archive <feature>`** — arquiva-**sem-integrar** (atômico, git-aware, commita o move escopado por pathspec): reservado a features legadas (sem branch `morph/{f}`) e faxina. O fechamento normal (arquiva **e** integra num passo) é `morph-spec finish`. É o que `/morph-archive` chama.
27
27
  - **`morph-spec worktree setup|link|remove|list`** — gerencia o ciclo do worktree por feature (`setup` cria `worktrees/{f}/` na branch `morph/{f}`, aloca o bloco de portas e escreve `.morph/worktree.env`; `link` religa infra compartilhada; `list` mostra fase/gate de cada worktree ativo). **Fechar a feature** continua sendo `morph-spec finish` (acima).
28
28
  - **`morph-spec worktree remove [feature] [--target <path>] [--discard-ignored] [--delete-branch]`** — a remoção **segura** de um worktree, sem fechar a feature. Desmonta as junctions de infra ANTES de deixar o git chegar no diretório e recusa se não conseguir **provar** que é seguro (PROVE-or-DECLINE) — `git worktree remove` cru, com ou sem `--force`, atravessa a junction e apaga o `.claude/`/`.morph/framework/`/`node_modules/` **reais da raiz**, saindo com sucesso. Use `--target` para worktree sem feature registrada (os que o `isolation: "worktree"` cria). A branch **sobrevive** por padrão: remover o worktree não é descartar o trabalho.
29
29
  - **`morph-spec handoff <feature>`** — regenera o documento de retomada (`.morph/features/{f}/handoff.md`): comprimido, derivado, aponta para os artefatos em vez de copiá-los. Gerado automaticamente em `setup`/`create --worktree`/`advance`/`approve`.
30
30
  - **`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.
31
- - **`morph-spec claim [feature] [--release] [--list] [--json]`** — posse **advisory** de feature. Cada sessão reclama sua feature ativa automaticamente (hook SessionStart) e renova o heartbeat a cada tool call; o registro vive em `<git-common-dir>/morph-spec/claims.json`, compartilhado por todos os worktrees. **`morph-spec claim <feature>` (sem flags) ADQUIRE** — se esta árvore já tinha claim vivo em outra feature, ele é liberado no mesmo passo (o CLI reporta "movido de X para Y"); é o jeito de trocar de feature no meio da tarefa sem a dança de dois passos `--release` + reclamar. `--list` força a leitura sem adquirir mesmo passando uma feature. **Claims avisam, nunca bloqueiam** — mas se o SessionStart avisar que outra sessão tem claim vivo, **confirme antes de `finish`/`archive`/`delete`**: duas sessões na mesma feature já custaram um recap.md inteiro e um sub-agent (o worktree sumiu debaixo dele). Claim órfão de sessão morta: `morph-spec claim <f> --release`.
31
+ - **`morph-spec claim [feature] [--release] [--list] [--all] [--json]`** — posse **advisory** de feature. Cada sessão reclama sua feature ativa automaticamente (hook SessionStart) e renova o heartbeat a cada tool call; o registro vive em `<git-common-dir>/morph-spec/claims.json`, compartilhado por todos os worktrees. **`morph-spec claim <feature>` (sem flags) ADQUIRE** — se esta árvore já tinha claim vivo em outra feature, ele é liberado no mesmo passo (o CLI reporta "movido de X para Y"); é o jeito de trocar de feature no meio da tarefa sem a dança de dois passos `--release` + reclamar. `--list` força a leitura sem adquirir mesmo passando uma feature. **Claims avisam, nunca bloqueiam** — mas se o SessionStart avisar que outra sessão tem claim vivo, **confirme antes de `finish`/`archive`/`delete`**: duas sessões na mesma feature já custaram um recap.md inteiro e um sub-agent (o worktree sumiu debaixo dele). Claim órfão de sessão morta: `morph-spec claim <f> --release`. **O board mostra só claims VIVOS** (dentro do TTL de 30 min): um claim morto não avisa mais nada, e deixá-lo listado transformava o quadro em ruído (features encerradas há 20h e 73h ainda aparecendo). `--all` mostra o registro inteiro; a contagem dos omitidos sai no rodapé, nunca em silêncio.
32
32
 
33
33
  ## Validação
34
34
 
@@ -36,7 +36,7 @@
36
36
  - **`morph-spec verify <feature> [task] [--json] [--skip-e2e] [--fresh-e2e]`** — cadeia determinística build+testes+validadores+**e2e** (código antes do juiz LLM); escopo de task quando informado, senão a feature inteira. O nó `e2e` é o único que **executa** a aplicação: sobe o `docker compose` do projeto, espera todo serviço ficar saudável e roda as specs Playwright quando existem (sem specs, ainda vale como smoke — "a app sobe" já pega o serviço não registrado que derruba o startup e passa batido no build). Aplicabilidade: escopo de feature (Gate 3) **sempre** roda; escopo de task roda para `group` `frontend`/`backend`/`infra` e dispensa `tests`/`docs`. **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.
37
37
  - **`morph-spec e2e up|status|down <feature>`** — ciclo de vida do stack de verificação, compartilhando toda a lógica com o nó `e2e` do verify. `up [--fresh]` reusa um stack quente **só** quando ele provadamente serve o código atual: o carimbo compara `HEAD` + `git status --porcelain` + `git diff HEAD`, então qualquer edição, commitada ou não, invalida o reuso (a exceção é o serviço com bind mount do código, que serve o disco por construção). Na dúvida, rebuild — o layer cache do Docker faz disso barato. `status [--json]` responde a pergunta que decide se um verde vale alguma coisa (no ar? saudável? construído do código atual?) e sai 0 só com os três; o `baseUrl` do payload é o endereço real da app — é dele que a `morph-review` tira a URL que vai no prompt do avaliador, em vez de deixá-lo adivinhar `localhost:3000`. `down [--volumes]` é best-effort e nunca bloqueia. Isolamento entre worktrees: `COMPOSE_PROJECT_NAME=morph-{feature}` separa os containers e o bloco de portas do fleet (`env-allocator`) separa as portas — um compose com porta fixa não pode ser isolado, e isso é avisado. O bloco vale nos **dois** caminhos (este comando e o nó do `verify`): `PORT`, `MORPH_PORT_BASE`, `ASPNETCORE_URLS` em dotnet, mais `PLAYWRIGHT_BASE_URL` apontando para a porta alocada. Por isso o `playwright.config.ts` precisa ler a base URL do ambiente em vez de cravá-la — com a URL cravada, a suíte bate numa porta vazia e o `ECONNREFUSED` se disfarça de app quebrada.
38
38
  - **`morph-spec doctor --worktrees [--prune] [--json]`** — reconcilia o registro do git × as pastas em disco × as features conhecidas. Worktrees órfãos acumulavam **invisíveis**: o `fleet` só reporta worktrees em branch `morph/*`, então um worktree em outra branch (resíduo de finish anterior) não aparecia em lugar nenhum. `--prune` roda o único reparo seguro (`git worktree prune` — registros de pastas que já sumiram); apagar pasta que ainda existe é reportado com o comando exato, nunca executado.
39
- - **`morph-spec report-check <feature> [task] [--json]`** — audita os relatórios de sub-agent contra o trabalho que realmente entrou. **Relatório ausente NÃO quer dizer que o agente falhou** — ele pode ter feito tudo e perdido o meio de gravar. O comando responde a pergunta certa (`work-landed-report-missing` → aceite degradado, NÃO re-despache; `no-work-no-report` → re-despache uma vez). Protocolo completo em MORPH.md §6.
39
+ - **`morph-spec report-check <feature> [task] [--json]`** — audita os relatórios de sub-agent contra o trabalho que realmente entrou. **Relatório ausente NÃO quer dizer que o agente falhou** — ele pode ter feito tudo e perdido o meio de gravar. E relatório PRESENTE não basta: `report-stale` é o relatório completo porém gravado ANTES do fim do trabalho (o agente relatou cedo e continuou escrevendo) — completo e mentindo. O comando responde a pergunta certa (`work-landed-report-missing` → aceite degradado, NÃO re-despache; `no-work-no-report` → re-despache uma vez; `report-stale` → atualize a partir do campo `observed`, NÃO re-despache). A evidência de `observed` vem do `events.jsonl` (todo `Write`/`Edit` observado na janela da task, cruzada com `task-times.json`), não de `outputs`, que é auto-declarado pelo próprio agente — writes do orquestrador sob `.morph/` e o do próprio relatório são excluídos. Sem telemetria no projeto, o comando volta ao comportamento anterior (fail-open). Protocolo completo em MORPH.md §6.
40
40
 
41
41
  ## Observabilidade
42
42
 
@@ -1,6 +1,6 @@
1
1
  # Rubrica Condensada — Eval Inline por Task
2
2
 
3
- Versão compacta das 4 rubricas completas de `framework/evals/rubrics/`, para o eval
3
+ Versão compacta das 4 rubricas completas de `.morph/framework/evals/rubrics/`, para o eval
4
4
  **inline por task** (`morph-eval` §2, escopo tarefa única). Mesmas dimensões, mesmos
5
5
  pesos, mesma régua — só menos texto por banda.
6
6
 
@@ -46,4 +46,4 @@ Arredonde para uma casa decimal. ≥9 aprova em silêncio; <9 alimenta o loop de
46
46
  ## Quando abrir a completa em vez desta
47
47
 
48
48
  Dúvida de banda, violação mecânica suspeita, ou escopo de fase (Gate 3) — abra
49
- `framework/evals/rubrics/{dimension}.md`. Esta condensada é o atalho do caso comum.
49
+ `.morph/framework/evals/rubrics/{dimension}.md`. Esta condensada é o atalho do caso comum.
@@ -51,3 +51,10 @@ Avalia se os testes cobrem os comportamentos esperados, são isolados e têm nom
51
51
  3. O que acontece quando uma dependência falha — isso está testado?
52
52
  4. Os testes podem rodar em qualquer ordem sem interferir entre si?
53
53
  5. Os nomes dos testes descrevem o comportamento esperado sem precisar ler o corpo?
54
+ 6. **Esta mudança é uma CORREÇÃO? Então: o que ela abriu?** Que comportamento antes correto pode ter
55
+ regredido por causa dela, e existe teste que pegaria essa regressão? Toda pergunta acima olha para
56
+ frente (o que a mudança deve fazer); esta olha para os lados. Numa feature real, três de três
57
+ rodadas de correção abriram um problema novo — o fix de um shadow abriu um laço de LLM a cada 15
58
+ min; o fecho de um fail-open abriu um P1; a correção desse P1 saiu com meio predicado. Nenhuma
59
+ apareceu no score, porque score não pergunta isso. **Sem resposta a esta pergunta numa correção,
60
+ o teto de Cobertura é 8.**
@@ -106,6 +106,93 @@ function diffPlanContract(oldDoc, newDoc, mutableFields) {
106
106
  return violations;
107
107
  }
108
108
 
109
+ /**
110
+ * O heading que abre o bloco de errata, no fim de um arquivo congelado.
111
+ *
112
+ * Congelar o CONTRATO não é a mesma coisa que congelar um ERRO DE FATO. Caso
113
+ * real: a spec afirmava, entre parênteses, que certos registros também eram
114
+ * escritos por humanos; o código mostrava que os três escritores eram do
115
+ * próprio motor. A afirmação era falsa sobre o código, não uma mudança de
116
+ * escopo — mas a trava cega não distingue as duas coisas, então a mentira
117
+ * ficava na spec e a correção morava em outro arquivo. Quem abrisse a spec
118
+ * primeiro lia a versão errada.
119
+ *
120
+ * A errata resolve isso SEM abrir o contrato: o corpo continua byte-idêntico e
121
+ * só se acrescenta texto ao fim, num bloco identificado. Escopo negociado ⇒
122
+ * revogar o gate. Fato errado sobre o código ⇒ errata.
123
+ */
124
+ const ERRATA_HEADING = '## Errata';
125
+
126
+ /** Índice onde começa o bloco de errata (-1 se não há). Só reconhece o heading em início de linha. */
127
+ function errataIndex(text) {
128
+ if (text.startsWith(ERRATA_HEADING)) return 0;
129
+ const at = text.indexOf(`\n${ERRATA_HEADING}`);
130
+ return at === -1 ? -1 : at + 1;
131
+ }
132
+
133
+ /**
134
+ * Corpo congelado = tudo ANTES do bloco de errata (ou o texto inteiro, se não
135
+ * há bloco), sem o branco final.
136
+ *
137
+ * O `trimEnd` não é frouxidão: separar o bloco por uma linha em branco é o
138
+ * formato markdown natural, e sem isso a primeira errata de todas seria
139
+ * recusada por "mudou o corpo" — quando o que mudou foi um `\n` de formatação.
140
+ * A garantia que importa (nenhum caractere SIGNIFICATIVO do texto aprovado
141
+ * muda) fica intacta, porque só espaço em branco terminal é ignorado.
142
+ */
143
+ function frozenBody(text) {
144
+ const at = errataIndex(text);
145
+ return (at === -1 ? text : text.slice(0, at)).trimEnd();
146
+ }
147
+
148
+ /**
149
+ * A mudança proposta é uma errata legítima?
150
+ *
151
+ * Três condições, todas necessárias — frouxo em qualquer uma delas e a trava
152
+ * cega vira uma trava que uma edição criativa atravessa:
153
+ * 1. o corpo congelado permanece BYTE-IDÊNTICO;
154
+ * 2. o resultado de fato tem um bloco `## Errata` (senão é edição do corpo);
155
+ * 3. a errata só CRESCE — o que já estava escrito nela não é reescrito.
156
+ *
157
+ * A (3) existe porque uma errata reescrita perde a serventia: o valor dela é
158
+ * ser trilha acumulada, não um segundo lugar editável.
159
+ *
160
+ * @returns {{ok: true} | {ok: false, why: string}}
161
+ */
162
+ function errataViolation(original, proposed) {
163
+ if (frozenBody(proposed) !== frozenBody(original)) {
164
+ return { ok: false, why: 'o corpo da spec mudou — errata só ACRESCENTA ao fim, nunca altera o texto aprovado' };
165
+ }
166
+ if (errataIndex(proposed) === -1) {
167
+ return { ok: false, why: `o conteúdo novo precisa estar sob um bloco \`${ERRATA_HEADING}\` no fim do arquivo` };
168
+ }
169
+ const oldAt = errataIndex(original);
170
+ if (oldAt !== -1) {
171
+ const oldErrata = original.slice(oldAt);
172
+ if (!proposed.slice(errataIndex(proposed)).startsWith(oldErrata)) {
173
+ return { ok: false, why: 'a errata existente foi reescrita — ela é append-only, entradas anteriores não se apagam' };
174
+ }
175
+ }
176
+ return { ok: true };
177
+ }
178
+
179
+ /**
180
+ * Texto proposto para um arquivo totalmente travado, a partir de Write ou Edit.
181
+ * @returns {string|null} null quando o payload não permite reconstruir (fail-open).
182
+ */
183
+ function proposedText(payload, toolName, original) {
184
+ if (toolName === 'Edit') {
185
+ const oldStr = payload?.tool_input?.old_string;
186
+ const newStr = payload?.tool_input?.new_string;
187
+ if (typeof oldStr !== 'string' || typeof newStr !== 'string') return null;
188
+ return payload?.tool_input?.replace_all
189
+ ? original.split(oldStr).join(newStr)
190
+ : original.replace(oldStr, newStr);
191
+ }
192
+ const content = payload?.tool_input?.content;
193
+ return typeof content === 'string' ? content : null;
194
+ }
195
+
109
196
  /**
110
197
  * @param {object} payload - parsed PreToolUse stdin payload
111
198
  * @returns {Promise<object|null>} `{decision:'block',reason}` or null (pass).
@@ -219,14 +306,38 @@ export async function run(payload) {
219
306
  );
220
307
  }
221
308
 
222
- // Fully-locked file (spec.md, mandate.md, design files): blanket block.
309
+ // ── Arquivo totalmente travado (spec.md, mandate.md, design) ────────────
310
+ // O corpo continua congelado. A ÚNICA passagem é a errata append-only:
311
+ // corpo byte-idêntico + texto novo sob `## Errata` no fim. Qualquer falha
312
+ // de leitura/reconstrução cai no bloqueio (o conservador aqui é BLOQUEAR —
313
+ // ao contrário do ramo do tasks.json, onde não conseguir diffar significa
314
+ // não ter como provar violação; aqui a regra base é "não se edita").
315
+ const toolName = getToolName(payload);
316
+ const absPath = resolveTargetPath(filePath, root);
317
+ let errataCheck = null;
318
+ try {
319
+ const original = readFileSync(absPath, 'utf-8');
320
+ const proposed = proposedText(payload, toolName, original);
321
+ if (proposed !== null) errataCheck = errataViolation(original, proposed);
322
+ } catch {
323
+ // arquivo ilegível → sem errata possível, segue para o bloqueio
324
+ }
325
+
326
+ if (errataCheck?.ok) {
327
+ logHookActivity('protect-spec-files', 'PreToolUse', 'ok');
328
+ return null;
329
+ }
330
+
223
331
  logHookActivity('protect-spec-files', 'PreToolUse', 'blocked');
224
332
  return blockResult(
225
333
  `MORPH-SPEC: '${filename}' is locked — the '${requiredGate}' gate has been approved.\n` +
226
334
  `Editing approved specs breaks the spec contract.\n\n` +
227
- `If changes are truly needed, ask the user to revoke the gate approval\n` +
228
- `in .morph/state.json (set approvals.${requiredGate} to false), make\n` +
229
- `your edits, then re-present the gate for approval.`
335
+ (errataCheck ? `Esta mudança não é uma errata válida: ${errataCheck.why}.\n\n` : '') +
336
+ `Corrigir um FATO ERRADO sobre o código (a spec afirma algo que o código\n` +
337
+ `desmente) não exige revogar o gate: acrescente um bloco \`${ERRATA_HEADING}\` no\n` +
338
+ `FIM do arquivo, deixando o corpo intacto — é append-only e fica na trilha.\n\n` +
339
+ `Mudar o ESCOPO acordado é outra coisa: peça ao usuário para revogar o gate\n` +
340
+ `'${requiredGate}', edite, e re-apresente o gate para aprovação.`
230
341
  );
231
342
  } catch {
232
343
  // Fail-open
@@ -160,10 +160,13 @@ export function getToolName(payload) {
160
160
  return payload?.tool_name || 'unknown';
161
161
  }
162
162
 
163
+ /** Assinatura de Bash: comando normalizado, truncado. Ver getCommandSignature. */
164
+ const BASH_SIGNATURE_MAX = 120;
165
+
163
166
  /**
164
167
  * Build a normalized signature for loop detection.
165
168
  * - Edit/Write: "Edit:normalized/file/path"
166
- * - Bash: "Bash:first two words" (e.g., "Bash:npm test")
169
+ * - Bash: "Bash:<normalized command, truncated>"
167
170
  * - Every other tool WITH input: "Tool:<truncated JSON of tool_input>"
168
171
  * - No tool_input at all: just the tool name
169
172
  *
@@ -175,6 +178,17 @@ export function getToolName(payload) {
175
178
  * false positive. Folding in the actual input (rather than hand-listing a
176
179
  * target field per tool) fixes it uniformly for every tool at once.
177
180
  *
181
+ * **Bash ficou de fora daquele fix (2026-07-22) e continuou colapsando em DUAS
182
+ * PALAVRAS**, o que é quase sempre o nome da ferramenta e nada mais:
183
+ * `npm test -- a`, `npm test -- b` e `npm run build` viravam `Bash:npm test` /
184
+ * `Bash:npm run`. Um ciclo legítimo de mutação — rodar o mesmo runner sobre
185
+ * alvos diferentes, que é exatamente como se testa se um teste morde — disparava
186
+ * o aviso de loop três vezes seguidas. Aviso que toca à toa é aviso que se
187
+ * aprende a ignorar, e aí ele não serve nem quando está certo.
188
+ *
189
+ * O discriminador agora é o comando inteiro (normalizado e truncado), mesma
190
+ * forma dos demais tools. Repetir o comando IDÊNTICO continua sendo loop.
191
+ *
178
192
  * @param {Object} payload - Hook payload from stdin
179
193
  * @returns {string} Signature string
180
194
  */
@@ -191,9 +205,10 @@ export function getCommandSignature(payload) {
191
205
  }
192
206
 
193
207
  if (tool === 'Bash') {
194
- const cmd = (input.command || '').trim();
195
- const words = cmd.split(/\s+/).slice(0, 2).join(' ');
196
- return words ? `Bash:${words}` : 'Bash';
208
+ // Espaços colapsados para que a mesma invocação escrita com espaçamento
209
+ // diferente continue casando — o alvo é o comando, não a digitação.
210
+ const cmd = (input.command || '').replace(/\s+/g, ' ').trim();
211
+ return cmd ? `Bash:${cmd.slice(0, BASH_SIGNATURE_MAX)}` : 'Bash';
197
212
  }
198
213
 
199
214
  try {
@@ -83,4 +83,5 @@ For each new feature slice, follow the patterns in these templates:
83
83
 
84
84
  1. **Read the template** to see the exact pattern
85
85
  2. **Generate code following the same structure** (naming, namespaces, imports)
86
- 3. Or render via CLI: `morph-spec template render <id> <output> '<variables>'`
86
+ 3. Or scaffold a whole VSA slice from the templates at once:
87
+ `morph-spec scaffold dotnet <feature> <slice-name>`