@polymorphism-tech/morph-spec 8.41.0 → 8.43.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 (102) hide show
  1. package/CLAUDE.md +14 -4
  2. package/README.md +3 -3
  3. package/bin/morph-spec.js +48 -2
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +6 -5
  8. package/framework/MORPH.md +10 -9
  9. package/framework/agents.json +7 -7
  10. package/framework/docs/CLI.md +10 -3
  11. package/framework/evals/proof-rubrics/mutation-proof.md +43 -13
  12. package/framework/hooks/README.md +1 -0
  13. package/framework/hooks/claude-code/core/chain.js +2 -0
  14. package/framework/hooks/claude-code/core/comment-guard.js +107 -0
  15. package/framework/hooks/claude-code/core/heavy-gate.js +72 -0
  16. package/framework/hooks/claude-code/core/heavy-release.js +34 -0
  17. package/framework/hooks/claude-code/core/worktree-context.js +21 -20
  18. package/framework/hooks/claude-code/core/worktree-create.js +4 -4
  19. package/framework/hooks/shared/agent-reap.js +89 -0
  20. package/framework/hooks/shared/comment-rules.js +171 -0
  21. package/{src/lib/validators/code-budget → framework/hooks/shared}/comment-scanner.js +2 -2
  22. package/{src/lib/ai → framework/hooks/shared}/comment-stripper.js +2 -1
  23. package/framework/hooks/shared/e2e-check.js +3 -1
  24. package/framework/hooks/shared/graph-refresh.js +1 -9
  25. package/framework/hooks/shared/heavy-command.js +128 -0
  26. package/framework/hooks/shared/heavy-config.js +65 -0
  27. package/framework/hooks/shared/heavy-holders.js +85 -0
  28. package/framework/hooks/shared/heavy-slots.js +207 -0
  29. package/framework/hooks/shared/process-alive.js +8 -0
  30. package/framework/hooks/shared/source-files.js +34 -0
  31. package/framework/hooks/shared/worktree-root.js +93 -0
  32. package/framework/schemas/config.schema.json +80 -0
  33. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  34. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  35. package/framework/skills/core/morph-apply/SKILL.md +11 -4
  36. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  37. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  38. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  39. package/framework/skills/core/morph-implement/SKILL.md +12 -5
  40. package/framework/skills/core/morph-machine/SKILL.md +46 -0
  41. package/framework/skills/core/morph-media/SKILL.md +1 -1
  42. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  43. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  44. package/framework/skills/core/morph-review/SKILL.md +45 -16
  45. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  46. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  47. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  48. package/framework/standards/PROVENANCE.json +10 -10
  49. package/framework/standards/STANDARDS.json +1 -1
  50. package/framework/standards/architecture/code-comments.md +9 -2
  51. package/framework/standards/frontend/design-system/ai-video-generation.md +2 -0
  52. package/framework/standards/frontend/scroll-driven/frame-scrub.md +11 -1
  53. package/package.json +1 -1
  54. package/src/commands/e2e/e2e.js +27 -8
  55. package/src/commands/mcp/mcp-setup.js +10 -9
  56. package/src/commands/project/doctor-procs.js +162 -0
  57. package/src/commands/project/doctor.js +42 -4
  58. package/src/commands/project/heavy.js +61 -0
  59. package/src/commands/project/init.js +1 -1
  60. package/src/commands/project/update.js +4 -0
  61. package/src/commands/project/worktree-reap.js +34 -0
  62. package/src/commands/project/worktree.js +78 -27
  63. package/src/commands/validation/mutate.js +200 -0
  64. package/src/commands/validation/verify.js +26 -1
  65. package/src/dashboard/scanner/features-scanner.js +4 -4
  66. package/src/dashboard/scanner/project-api.js +4 -4
  67. package/src/lib/ai/code-audit.js +1 -1
  68. package/src/lib/ai/registry-audit.js +1 -1
  69. package/src/lib/detectors/claude-config-detector.js +30 -1
  70. package/src/lib/heavy/cleanup.js +70 -0
  71. package/src/lib/heavy/gate.js +96 -0
  72. package/src/lib/installers/mcp-installer.js +15 -2
  73. package/src/lib/mutation/adapters.js +90 -0
  74. package/src/lib/mutation/commands.js +60 -0
  75. package/src/lib/mutation/manifest.js +138 -0
  76. package/src/lib/mutation/plan.js +36 -0
  77. package/src/lib/mutation/report.js +66 -0
  78. package/src/lib/mutation/runner.js +351 -0
  79. package/src/lib/mutation/schemata.js +60 -0
  80. package/src/lib/mutation/snapshot.js +69 -0
  81. package/src/lib/mutation/subset.js +75 -0
  82. package/src/lib/mutation/trx.js +51 -0
  83. package/src/lib/procs/classify.js +91 -0
  84. package/src/lib/procs/list.js +124 -0
  85. package/src/lib/procs/mcp-config.js +103 -0
  86. package/src/lib/procs/prune.js +25 -0
  87. package/src/lib/procs/wslconfig.js +70 -0
  88. package/src/lib/projects/registry.js +6 -2
  89. package/src/lib/tasks/dotnet-test-introspection.js +1 -1
  90. package/src/lib/tasks/linux-runner.js +30 -3
  91. package/src/lib/tasks/test-runner.js +1 -1
  92. package/src/lib/validators/code-budget/code-budget-validator.js +4 -6
  93. package/src/lib/validators/code-budget/index.js +1 -1
  94. package/src/lib/validators/reuse/symbol-scanner.js +1 -28
  95. package/src/lib/validators/verify-runner.js +26 -8
  96. package/src/lib/worktree/agent-reaper.js +247 -0
  97. package/src/lib/worktree/dirs.js +3 -32
  98. package/src/lib/worktree/vscode-settings.js +113 -0
  99. package/src/lib/worktree/worktree-audit.js +49 -5
  100. package/src/lib/worktree/worktree-include.js +46 -0
  101. package/src/scripts/setup-infra.js +4 -0
  102. package/src/utils/hooks-installer.js +14 -6
@@ -4,7 +4,7 @@ description: Guia a execução de cada tarefa de implementação com standard e
4
4
  version: core
5
5
  when-to-invoke: ao iniciar cada task de implementação listada em tasks.json
6
6
  user-invokable: false
7
- cliVersion: "8.41.0"
7
+ cliVersion: "8.43.0"
8
8
  ---
9
9
 
10
10
  # morph-implement
@@ -94,7 +94,9 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
94
94
  Comentário: o padrão é nenhum. Nome, tipo e teste explicam o código; sem
95
95
  `///`/JSDoc em membro de aplicação, cabeçalho, banner de seção ou história
96
96
  da task. Só o porquê que o código não mostra, em uma linha
97
- (`architecture-code-comments`).
97
+ (`architecture-code-comments`). O elo com a pós-condição vai no nome do
98
+ teste; a densidade do arquivo vizinho não é modelo. O hook `comment-guard`
99
+ bloqueia a edição que adiciona história, banner ou bloco longo.
98
100
 
99
101
  Contrato do relatório final:
100
102
  1. GRAVE `.morph/features/{feature}/3-implement/reports/[ID].md` ANTES de
@@ -130,7 +132,10 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
130
132
  por decisão sua; ao concluir, o relatório final substitui o parcial, sem o
131
133
  `PARCIAL`. Sem limite declarado, bastam os commits por marco.
132
134
  6. PROCESSOS: NUNCA `taskkill /IM node.exe` (derruba os MCPs de todas as
133
- sessões). Encerre dev server pelo PID que você mesmo subiu.
135
+ sessões). Encerre dev server pelo PID que você mesmo subiu. Build e
136
+ suíte pesados (`dotnet build`, `dotnet test`, `docker compose up`) rodam
137
+ por `morph-spec heavy run -- <comando>`: exit 3 é fila cheia, não erro do
138
+ código — espere e repita.
134
139
  7. RETORNO: o relatório no arquivo canônico é o retorno. `SendMessage` não é
135
140
  substituto do relatório e não deve ser chamado.
136
141
  8. MUTAÇÃO: nunca altere, reverta, mova ou apague código de produção para ver
@@ -151,7 +156,9 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
151
156
  deixa o disco a meio caminho — e um relatório lido sobre uma árvore que ninguém conferiu avalia
152
157
  um estado que ninguém quis. Árvore inesperadamente suja ou `markers` não vazio é falha de
153
158
  protocolo: registre no recap antes de pontuar. Essa saída serve ao passo 7 enquanto a árvore
154
- não mudar; depois de uma correção sua, rode de novo.
159
+ não mudar; depois de uma correção sua, rode de novo. Dispatch com `isolation: "worktree"`:
160
+ depois de ler o relatório, `morph-spec worktree reap --json` remove o worktree do agent que parou
161
+ limpo e sem commit exclusivo; o que fica vem listado com a razão.
155
162
 
156
163
  Após o retorno do dispatch: **leia primeiro** `.morph/features/{feature}/3-implement/reports/[ID].md`
157
164
  — é o valor de retorno autoritativo. Trate a mensagem de texto do sub-agent, se chegou inteira,
@@ -265,7 +272,7 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
265
272
  > tasks.json + recap.md + `taskScores` do feature.json. **NÃO escreva o trace
266
273
  > manualmente** — a escrita manual foi removida da cerimônia por task.
267
274
 
268
- 9. Quando invocada por `morph-apply`, retornar o controle à skill orquestradora — ela decide qual task vem em seguida via `getReadyTasks(data)`. Não rode a suíte de testes inteira do projeto por conta própria ao fim da task quando invocada pela `morph-apply` — a verificação determinística (`morph-spec verify {feature} {task}`) é do orquestrador, logo após seu retorno (ver morph-apply §3a). **Única exceção:** a execução de evidência do passo 5 — só o nó `evals`, filtrado, com `--skip-e2e`, nunca persistida (`--no-persist`); ela não é a verificação da task e não substitui a do orquestrador. **A divisão:** no loop por task o recorte é **automático** — o `group` da task escolhe as suítes e os arquivos de teste dos `outputs` (passo 8) estreitam o comando dentro de cada uma (o relógio de uma task não paga a suíte inteira); `--filter {ids}` só força a escolha de suítes (um nó não casado aparece como `skip` nomeando o filtro, e um filtro que não casa nada é erro de uso, exit 2) e `--full` desliga o recorte. O sub-agent despachado roda só o filtro dos próprios testes, uma vez (cláusula 3), e nunca o `verify`. A suíte **inteira** roda no Gate 3 e no CI, sem filtro — `verify {feature}` nunca recorta. A suíte completa de um projeto grande não cabe numa invocação de Bash do harness — rode em background ou por partes, nunca "suba o limite" (não há constante de timeout global no CLI). Detalhe e forma de `project.tests[]`: `.morph/framework/CLI.md`, seção "Suítes de teste". Quando invocada manualmente, verificar se há próxima task ready e iniciá-la imediatamente aplicando esta skill desde o passo 1 (auto-advance). Pause apenas se encontrar ambiguidade bloqueante não coberta pelo mandate.
275
+ 9. Quando invocada por `morph-apply`, retornar o controle à skill orquestradora — ela decide qual task vem em seguida via `getReadyTasks(data)`. Não rode a suíte de testes inteira do projeto por conta própria ao fim da task quando invocada pela `morph-apply` — a verificação determinística (`morph-spec verify {feature} {task}`) é do orquestrador, logo após seu retorno (ver morph-apply §3a). **`verify` com exit 3** (`status: "busy"`) é fila de slot pesado cheia, não vermelho: nada foi verificado e nada foi gravado — repita a mesma chamada em vez de abrir uma tentativa de correção. **Única exceção:** a execução de evidência do passo 5 — só o nó `evals`, filtrado, com `--skip-e2e`, nunca persistida (`--no-persist`); ela não é a verificação da task e não substitui a do orquestrador. **A divisão:** no loop por task o recorte é **automático** — o `group` da task escolhe as suítes e os arquivos de teste dos `outputs` (passo 8) estreitam o comando dentro de cada uma (o relógio de uma task não paga a suíte inteira); `--filter {ids}` só força a escolha de suítes (um nó não casado aparece como `skip` nomeando o filtro, e um filtro que não casa nada é erro de uso, exit 2) e `--full` desliga o recorte. O sub-agent despachado roda só o filtro dos próprios testes, uma vez (cláusula 3), e nunca o `verify`. A suíte **inteira** roda no Gate 3 e no CI, sem filtro — `verify {feature}` nunca recorta. A suíte completa de um projeto grande não cabe numa invocação de Bash do harness — rode em background ou por partes, nunca "suba o limite" (não há constante de timeout global no CLI). Detalhe e forma de `project.tests[]`: `.morph/framework/CLI.md`, seção "Suítes de teste". Quando invocada manualmente, verificar se há próxima task ready e iniciá-la imediatamente aplicando esta skill desde o passo 1 (auto-advance). Pause apenas se encontrar ambiguidade bloqueante não coberta pelo mandate.
269
276
 
270
277
  ## Output esperado
271
278
  - Código implementado conforme o standard consultado (direto ou via sub-agent)
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: morph-machine
3
+ description: Cuida da RAM e da CPU da máquina enquanto o morph-spec trabalha — fila de builds e testes pesados (heavy), processos órfãos, worktrees de sub-agent que sobram. Invoque quando a máquina ou o editor estiver lento ou sem RAM, quando um comando voltar "⏳ não rodou" ou sair com exit 3 (fila cheia), quando sobrar agent-* em worktrees/, ou antes de despachar vários sub-agents que compilam ou testam em paralelo.
4
+ version: core
5
+ when-to-invoke: quando a máquina está lenta ou sem RAM, um comando pesado esperou ou foi recusado pela fila, sobrou worktree de sub-agent, ou vários sub-agents pesados vão rodar em paralelo
6
+ user-invokable: false
7
+ cliVersion: "8.43.0"
8
+ ---
9
+
10
+ # morph-machine
11
+
12
+ ## Objetivo
13
+
14
+ Manter a máquina do dev utilizável enquanto o trabalho paralelo do morph-spec roda. Você aplica isto sozinho quando o sintoma aparece; o usuário não precisa pedir.
15
+
16
+ ## Regras que não se negociam
17
+
18
+ - **Nunca** `taskkill /IM`, `pkill`, `killall` nem `Stop-Process -Name`: derrubam os MCPs de todas as sessões abertas. O único que encerra processo é `doctor --procs --prune`, e por PID.
19
+ - **Fila cheia não é erro de código.** `⏳ … não rodou`, `slots pesados ocupados` e exit 3 querem dizer "espere e repita o MESMO comando". Não entra no loop de correção, não conta como tentativa e não é vermelho no `verify`.
20
+ - **Nunca remova worktree** por `git worktree remove`, `rm -rf` ou `Remove-Item -Recurse`: atravessa a junction e apaga o conteúdo real da raiz. Só `finish`, `worktree remove` ou `worktree reap`.
21
+ - **Destrutivo pergunta antes.** `doctor --procs --prune` e `worktree reap` (sem `--dry-run`): mostre a lista e use `AskUserQuestion`.
22
+
23
+ ## Sintoma → comando
24
+
25
+ | Sintoma | Comando | Como ler e o que fazer |
26
+ |---|---|---|
27
+ | Máquina ou editor lento, pouca RAM | `morph-spec doctor --procs` (só lê) | Cada linha diz órfão ou protegido, com a razão. Sem órfão, a pressão vem de fora (vmmem do WSL, IDE): diga isso e passe a sugestão de `.wslconfig` que o relatório traz. Com órfão, mostre a lista e ofereça `--prune`. |
28
+ | Comando voltou `⏳ … não rodou` ou exit 3 | `morph-spec heavy status` | Mostra quem segura cada vaga, há quanto tempo, e a RAM livre. A vaga solta sozinha (processo morto ou timeout). Espere e repita. Se a causa for RAM e nenhuma vaga estiver presa, rode `doctor --procs`. |
29
+ | Vão rodar vários sub-agents pesados em paralelo | `morph-spec heavy status` antes | São 2 vagas por padrão. O que não couber espera na fila sem falhar, mas cada espera custa turnos: despache só o que cabe. |
30
+ | Build ou teste pesado avulso, fora do `verify` | `morph-spec heavy run -- <comando>` | Ou ligue o `heavy.gateBash` (abaixo) e o hook cuida disso. |
31
+ | Sobrou `agent-*` em `worktrees/` ou branch `morph-task/agent-*` | `morph-spec worktree reap --dry-run`, depois `worktree reap` | Só remove o que parou, está limpo e não tem commit exclusivo; o resto volta com a razão (`dirty`, `locked`…). `doctor --worktrees` lista tudo, inclusive árvore fora do contêiner. |
32
+ | A worktree precisa de arquivo fora do git (`.env`, segredos) | `.worktreeinclude` na raiz do repo | Um padrão por linha, sintaxe do gitignore. Copia só o que casa **e** é ignorado; nunca `node_modules`, build ou `.git`. O `setup` e o `provision` aplicam. |
33
+ | Editor pesado por causa das worktrees | `worktree.root` em `.morph/config/config.json` | Põe as worktrees fora da árvore aberta (caminho absoluto fora do projeto). `init` e `update` já gravam as exclusões no `.vscode/settings.json`. |
34
+
35
+ ## Ligar o `heavy.gateBash`
36
+
37
+ Por padrão só o `verify`, o `e2e up` e o `heavy run` passam pela fila. O `dotnet build`, `dotnet test` ou `npm test` cru de um sub-agent não passa. Com `"heavy": { "gateBash": true }` em `.morph/config/config.json` (depois `morph-spec update`), um hook toma a vaga antes desses comandos e a devolve depois; sem vaga no prazo, a chamada é recusada com a causa.
38
+
39
+ Recomende (`AskUserQuestion`, com a opção recomendada primeiro) quando sub-agents rodam build ou teste cru em paralelo, quando há mais de uma sessão na máquina, ou quando já houve queda por RAM. **Nunca ligue sem perguntar:** o efeito é uma chamada recusada quando a fila enche.
40
+
41
+ Limites: não cobre `run_in_background`, PowerShell nem comando fora da lista; uma chamada negada por regra ou interrompida segura a vaga até o timeout dela.
42
+
43
+ ## Referências
44
+
45
+ - `.morph/framework/CLI.md` — seções `heavy`, `worktree reap`, `doctor --procs` e `worktree.root`.
46
+ - `MORPH.md` §2.2 (worktrees) e a cláusula 6 dos templates de dispatch (PROCESSOS).
@@ -4,7 +4,7 @@ description: Conduz a AUTORIA de mídia gerada por IA — escolher o modelo pelo
4
4
  version: core
5
5
  when-to-invoke: quando a tarefa é PRODUZIR imagem ou vídeo por IA (catálogo, marketing, asset de LP) e alguém precisa decidir modelo, prompt e aprovação
6
6
  user-invokable: true
7
- cliVersion: "8.41.0"
7
+ cliVersion: "8.43.0"
8
8
  stacks:
9
9
  - dotnet
10
10
  - nextjs
@@ -4,7 +4,7 @@ description: Guia a produção dos artefatos de planejamento de uma feature (use
4
4
  version: core
5
5
  when-to-invoke: ao iniciar a fase plan de uma feature, após o Gate 1 (proposal aprovado)
6
6
  user-invokable: true
7
- cliVersion: "8.41.0"
7
+ cliVersion: "8.43.0"
8
8
  ---
9
9
 
10
10
  # morph-plan
@@ -4,7 +4,7 @@ description: Orquestra a fase de proposta+plano de uma feature morph-spec. Encad
4
4
  version: core
5
5
  when-to-invoke: ao receber instrução "use /morph-proposal" do usuário, ou ao iniciar uma nova feature
6
6
  user-invokable: true
7
- cliVersion: "8.41.0"
7
+ cliVersion: "8.43.0"
8
8
  ---
9
9
 
10
10
  # morph-proposal
@@ -4,7 +4,7 @@ description: Conduz a fase de revisão e produz o review-report.md para o Gate 3
4
4
  version: core
5
5
  when-to-invoke: ao iniciar a fase review, após todas as tasks de tasks.json estarem com status `done`
6
6
  user-invokable: true
7
- cliVersion: "8.41.0"
7
+ cliVersion: "8.43.0"
8
8
  ---
9
9
 
10
10
  # morph-review
@@ -63,7 +63,7 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
63
63
  rodada.
64
64
 
65
65
  **Despache o avaliador independente** — o score do Gate 3 nunca é auto-atribuído por quem implementou:
66
- - Rode `morph-spec verify {feature} --json` (escopo feature) e capture o JSON — é a saída real de build+testes+validadores+e2e que você anexa ao prompt do avaliador independente (`morph-evaluator`). **Sem `--filter`, sempre:** o `--filter` é do loop por task, onde o relógio de uma task não paga a suíte inteira; a prova que o avaliador lê tem de cobrir **todos** os nós de `nodes.tests.runs[]` (uma suíte filtrada aparece como `skip` nomeando o filtro — Gate 3 aberto sobre isso é verde sobre código não testado). Se a suíte inteira não couber numa chamada, rode-a em background; não a encolha com `--filter`. `verify {feature}` (sem task) **nunca recorta** — o recorte automático é só do escopo de task —, e um `verify {feature} --filter …` nem persiste o stamp (`persisted: false`, `reason: 'filtered'`) — o mesmo vale para `--skip-tests`/`--skip-build` no escopo de feature. Rode-o **depois** da última task: o `gate-check {feature} review` pausa quando o último stamp `verification` é de escopo `task:{id}`, porque aquela rodada cobriu só a task. Uma rodada por árvore: se o JSON vier com `repeatOf`, nada mudou fora de `.morph/` desde o verify de feature anterior — use aquele resultado em vez de rodar de novo; depois de uma correção a árvore mudou, e a nova rodada é a prova.
66
+ - Rode `morph-spec verify {feature} --json` (escopo feature; exit 3 / `status: "busy"` é fila de slot pesado cheia, não vermelho: repita a chamada) e capture o JSON — é a saída real de build+testes+validadores+e2e que você anexa ao prompt do avaliador independente (`morph-evaluator`). **Sem `--filter`, sempre:** o `--filter` é do loop por task, onde o relógio de uma task não paga a suíte inteira; a prova que o avaliador lê tem de cobrir **todos** os nós de `nodes.tests.runs[]` (uma suíte filtrada aparece como `skip` nomeando o filtro — Gate 3 aberto sobre isso é verde sobre código não testado). Se a suíte inteira não couber numa chamada, rode-a em background; não a encolha com `--filter`. `verify {feature}` (sem task) **nunca recorta** — o recorte automático é só do escopo de task —, e um `verify {feature} --filter …` nem persiste o stamp (`persisted: false`, `reason: 'filtered'`) — o mesmo vale para `--skip-tests`/`--skip-build` no escopo de feature. Rode-o **depois** da última task: o `gate-check {feature} review` pausa quando o último stamp `verification` é de escopo `task:{id}`, porque aquela rodada cobriu só a task. Uma rodada por árvore: se o JSON vier com `repeatOf`, nada mudou fora de `.morph/` desde o verify de feature anterior — use aquele resultado em vez de rodar de novo; depois de uma correção a árvore mudou, e a nova rodada é a prova.
67
67
  - **Confira o nó `e2e` do verify antes de despachar.** Ele carrega o estado do stack:
68
68
  - `pass` → a aplicação subiu e está saudável; o avaliador pode navegá-la. **Passe a URL no prompt**
69
69
  — cada feature roda no seu bloco de portas, não em `localhost:3000`; pegue de
@@ -174,21 +174,48 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
174
174
  MUTAÇÃO — o método, e a ordem dele importa:
175
175
  - COMMITE O FIX ANTES DE MUTAR. Se a sessão morrer no meio, o
176
176
  que sobrevive é a mutação, não a correção.
177
- - Ancore no CALL SITE que carrega a decisão, não no `if`
178
- refatorado. Uma rodada POR CLÁUSULA de um predicado composto.
179
- - Rode a suíte INTEIRA (sem filtro): só ela autoriza a frase
180
- "derruba N de M". Filtro estreito é prova vácua.
177
+ - VOCÊ ESCOLHE os mutantes: ancore no CALL SITE que carrega a
178
+ decisão, não no `if` refatorado. Uma rodada POR CLÁUSULA de um
179
+ predicado composto — cada cláusula é um mutante próprio.
180
+ - ESCREVA `.morph/features/{feature}/4-review/mutants.json`:
181
+ {"mutants":[{"id":"M1","file":"src/x.cs","line":42,
182
+ "find":"a && b","replace":"a"}]}. `find` começa na `line` e é
183
+ único nela; arquivo:linha, diff e md5 saem da ferramenta.
184
+ Com a ferramenta Write, se você a tem; só com Bash, heredoc
185
+ de delimitador entre aspas simples. O transporte do Bash
186
+ reduz `\\` a `\` e decodifica escapes unicode, então escreva
187
+ cada barra invertida do código como `{{BS}}` (a ferramenta a
188
+ expande): atravessa qualquer transporte. `find` que deixa de
189
+ bater com o arquivo é recusado com o erro, nunca aplicado.
190
+ - RODE `morph-spec mutate {feature}` UMA vez, com o Bash. Ela
191
+ faz o lote inteiro: um build só (chaves por variável de
192
+ ambiente; o que a chave não compila cai para rebuild real),
193
+ subconjunto de testes primeiro — escolhido por ela, não por
194
+ você — e, para CADA sobrevivente, o mutante compilado de
195
+ verdade contra a suíte INTEIRA. Só esse caminho produz
196
+ "sobreviveu"; "derruba K de M" sempre diz o escopo do K
197
+ (≥K no subconjunto, K na suíte inteira); na suíte inteira a
198
+ falha tem de se repetir, senão é instabilidade e o mutante
199
+ sobrevive. Sem a ferramenta,
200
+ mute e rode a suíte INTEIRA à mão, um mutante por vez.
181
201
  - LEIA O MOTIVO da falha, não só o md5: md5 prova que ALGO
182
- mudou, jamais O QUÊ.
183
- - REVERTA por snapshot/restore de BYTES (cópia feita ANTES) +
184
- `os.utime` para devolver o mtime, e confirme por md5. NUNCA
202
+ mudou, jamais O QUÊ. Falha de build não é morte, e
203
+ `inconclusive` não conta como nada.
204
+ - A REVERSÃO é da ferramenta: bytes do snapshot com md5
205
+ conferido, por escrita NOVA, rebuild forçado e a DLL final
206
+ igual (md5) à construída antes do 1º mutante. NUNCA `os.utime`
207
+ para devolver o mtime: o MSBuild acha o binário do último
208
+ mutante em dia e a rodada-base roda sobre ele. NUNCA
185
209
  `git checkout --`: ele descarta trabalho não commitado de
186
210
  outra pessoa — aconteceu de verdade num Gate 3 desta epic.
187
- - TERMINE com `git status --porcelain src/` VAZIO. Mutação
188
- esquecida em produção é o pior desfecho deste método.
189
- Grave no relatório a seção `## Mutações`, uma linha por mutação,
190
- com os CINCO campos: arquivo:linha | diff | md5 antes/depois |
191
- quantos testes derrubou (sobre o total da suíte) | o MOTIVO
211
+ Rodada que morreu no meio: `morph-spec mutate --restore`.
212
+ - TERMINE com `git status --porcelain src/` VAZIO e exit 0. Exit
213
+ 4 é inseguro (arquivo não restaurado, árvore ou binário
214
+ diferentes): resolva antes de pontuar.
215
+ Cole no relatório a seção `## Mutações` que a ferramenta imprime
216
+ (ela também a grava em `4-review/mutation-section.md`), uma linha
217
+ por mutação, com os CINCO campos: arquivo:linha | diff | md5
218
+ antes/depois | quantos testes derrubou (com o escopo) | o MOTIVO
192
219
  literal da falha. Feature sem código de produção declara a seção
193
220
  com a linha `não aplicável — feature sem código de produção`. A
194
221
  seção é OBRIGATÓRIA: sem ela o `morph-spec gate-check` PAUSA o
@@ -294,6 +321,7 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
294
321
  > diff antes). É a mesma proibição que o prompt do passo 4 já dá ao avaliador: aqui ela vale
295
322
  > para VOCÊ.
296
323
  - **Leia primeiro** `.morph/features/{feature}/4-review/evaluator-report.md` — é o valor de retorno autoritativo, não a mensagem de texto do dispatch (que é cortesia, útil só quando chega inteira). Os scores por dimensão e o composto do review-report vêm **desse arquivo**, não da sua auto-avaliação. Se o arquivo vier ausente, oco, ou sem os scores por dimensão, registre a falha de protocolo na seção de método do review-report e re-despache.
324
+ - Com o relatório lido, rode `morph-spec worktree reap --json`: a árvore do avaliador que parou limpa sai sozinha; a que ficou com trabalho vem listada com a razão.
297
325
  - **`PARCIAL` na 1ª linha = o avaliador parou antes do fim** (limite de turnos; o `report-check` devolve `evaluator.partial: true`). Não leia score dele — o parcial não tem `**Composite-Score:**`, e o `gate-check` pausaria de qualquer jeito — e não re-despache frio: um avaliador novo refaz a rodada inteira (probes, mutações, navegação). Retome o MESMO avaliador por `SendMessage`, como no passo 6, colando `tree.dirty` e `tree.commits` do `report-check` no recado; ele completa o relatório, e o final substitui o parcial. Só se o id não retomar, despache um novo pelo caminho normal — o parcial no disco é resíduo, não insumo, como o prompt já diz. Antes de qualquer das duas, a higiene da árvore acima continua valendo: um avaliador cortado no meio pode ter deixado mutação no disco.
298
326
 
299
327
  5. Produza `review-report.md` em `.morph/features/{feature}/4-review/` com:
@@ -318,9 +346,10 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
318
346
 
319
347
  | # | Arquivo:linha | Mutação (diff) | md5 antes | md5 depois | Testes derrubados | Motivo da falha |
320
348
  |---|---|---|---|---|---|---|
321
- | M1 | src/lib/x.js:42 | `- if (a && b)` / `+ if (a)` | abc… | def… | 3 de 2684 | `expected 3 got 0` em x.test.js:88 |
349
+ | M1 | src/lib/x.js:42 | `- if (a && b)` / `+ if (a)` | abc… | def… | ≥3 de 212 (subconjunto) | `expected 3 got 0` em x.test.js:88 |
350
+ | M2 | src/lib/y.js:7 | `- return ok` / `+ return true` | 123… | 456… | 0 de 2684 (SOBREVIVEU, suíte inteira) | nenhum teste falhou na suíte inteira |
322
351
 
323
- **Reversão:** snapshot/restore de bytes + `os.utime`; `git status --porcelain src/` vazio.
352
+ **Reversão:** snapshot/restore de bytes por escrita nova; DLL final igual (md5); `git status --porcelain src/` vazio.
324
353
  **Proof-Scores:** mutation=9 verde=8 fiacao=9 guard=7
325
354
  ```
326
355
 
@@ -4,7 +4,7 @@ description: Varre o codebase existente, detecta stack atual, compara com o stac
4
4
  version: core
5
5
  when-to-invoke: ao iniciar uma feature em projeto brownfield (codebase já existente)
6
6
  user-invokable: true
7
- cliVersion: "8.41.0"
7
+ cliVersion: "8.43.0"
8
8
  ---
9
9
 
10
10
  # morph-stack-scan
@@ -4,7 +4,7 @@ description: Localiza um standard específico via busca server-side (morph-spec
4
4
  version: core
5
5
  when-to-invoke: quando precisar consultar um standard específico durante a implementação
6
6
  user-invokable: true
7
- cliVersion: "8.41.0"
7
+ cliVersion: "8.43.0"
8
8
  ---
9
9
 
10
10
  # morph-standards
@@ -4,7 +4,7 @@ description: Conduz a fase opcional de UI/UX — design system, mockups, compone
4
4
  version: core
5
5
  when-to-invoke: ao tomar o ramo uiux (morph-spec advance --design), depois do Gate 1 e antes do morph-plan
6
6
  user-invokable: true
7
- cliVersion: "8.41.0"
7
+ cliVersion: "8.43.0"
8
8
  ---
9
9
 
10
10
  # morph-uiux
@@ -8,15 +8,15 @@
8
8
  "path": "skills/morph-lp-cinematic",
9
9
  "envVar": "MORPH_HUB",
10
10
  "defaultCheckout": "R:/Polymorphism Tech/repos/morph-HUB",
11
- "sourceTree": "e43114d4b28ff55680a5355cce27d31ffa74f22f",
12
- "sourceCommit": "465c9b0",
11
+ "sourceTree": "2987b44241cae479b1aedc63f689cdcd6ef6fb94",
12
+ "sourceCommit": "c4c9164",
13
13
  "distilledOn": "2026-08-21",
14
14
  "distilledFrom": [
15
15
  "references/build-stack.md",
16
16
  "references/component-recipes.md",
17
17
  "references/premium-finish.md",
18
18
  "references/seo-and-qa.md",
19
- "references/gemini-image-prompt.md",
19
+ "references/image-prompt.md",
20
20
  "references/seedance-scrub-prompt.md",
21
21
  "references/seedance-loop-prompt.md"
22
22
  ],
@@ -25,7 +25,7 @@
25
25
  "references/copy-and-messaging.md",
26
26
  "references/section-blueprint.md"
27
27
  ],
28
- "notDistilledReason": "Deliberate split: a standard carries knowledge, a skill carries process. Deploy and copy strategy stay with the skill. `references/gates.md` LEFT this list on 2026-09-09: the reason that kept it out was \"a skill carries process\", and the destination is now a skill — see the \"cinematic-lp\" source below, which distils it into morph-media.",
28
+ "notDistilledReason": "Deliberate split: a standard carries knowledge, a skill carries process. Deploy and copy strategy stay with the skill. `references/gates.md` LEFT this list on 2026-09-09: the reason that kept it out was \"a skill carries process\", and the destination is now a skill — see the \"cinematic-lp\" source below, which distils it into morph-media. Review of 2026-09-29 (source c4c9164): only tool-independent facts were ported (the scrub_frames manifest contract for frame-scrub, chained clips for ai-video-generation); the rest was already in the standards or stays with the skill (ffmpeg-to-media_tool swaps, model prices, SKILL.md, gates.md, assets/).",
29
29
  "standards": [
30
30
  "frontend-scroll-driven-frame-scrub",
31
31
  "frontend-scroll-driven-smooth-scroll",
@@ -65,8 +65,8 @@
65
65
  "path": "skills/cinematic-lp/references",
66
66
  "envVar": "MORPH_HUB",
67
67
  "defaultCheckout": "R:/Polymorphism Tech/repos/morph-HUB",
68
- "sourceTree": "57a1734bc17651a6fed381a6e817c8b0c0a89edd",
69
- "sourceCommit": "b6bbfff",
68
+ "sourceTree": "3c557425d649ed477bf6645b9297b6d1d72ae4a3",
69
+ "sourceCommit": "c4c9164",
70
70
  "distilledOn": "2026-09-09",
71
71
  "distilledFrom": [
72
72
  "gates.md"
@@ -85,7 +85,7 @@
85
85
  "video-loop-prompt.md",
86
86
  "video-scrub-prompt.md"
87
87
  ],
88
- "notDistilledReason": "Only the HITL gate mechanics were distilled: the N-option picker, \"rejection goes back to the same step\", and \"show MOVEMENT, never a still\". The rest is either already a standard (the frame-scrub / smooth-scroll / premium-finish family, from the morph-lp-cinematic source above) or build/deploy strategy that belongs to the calling workflow.",
88
+ "notDistilledReason": "Only the HITL gate mechanics were distilled: the N-option picker, \"rejection goes back to the same step\", and \"show MOVEMENT, never a still\". The rest is either already a standard (the frame-scrub / smooth-scroll / premium-finish family, from the morph-lp-cinematic source above) or build/deploy strategy that belongs to the calling workflow. Review of 2026-09-29 (source c4c9164): gates.md did not change; the changes were in image-prompt.md and mcp-setup.md, both not distilled. Re-pinned only.",
89
89
  "standards": [],
90
90
  "skills": [
91
91
  "morph-media"
@@ -97,8 +97,8 @@
97
97
  "path": "skills/brand-design/references",
98
98
  "envVar": "MORPH_HUB",
99
99
  "defaultCheckout": "R:/Polymorphism Tech/repos/morph-HUB",
100
- "sourceTree": "d8801b8c277f63b06e1a22f57563ec468e7d4d9a",
101
- "sourceCommit": "b6bbfff",
100
+ "sourceTree": "7b637a08e931d8b7ba5be7fc75b851d89cd6e6f4",
101
+ "sourceCommit": "c4c9164",
102
102
  "distilledOn": "2026-09-09",
103
103
  "distilledFrom": [
104
104
  "image-generation.md"
@@ -109,7 +109,7 @@
109
109
  "example-identidade-visual.md",
110
110
  "phases.md"
111
111
  ],
112
- "notDistilledReason": "Only the image-per-phase map and the \"concept, never the final vector\" rule were distilled. The narrative prompt recipes were NOT copied verbatim: they are bound to one brand pipeline, and morph-media carries the prompt STRUCTURE instead, which survives the model changing. Brand book, checklist and phases are the process of another skill.",
112
+ "notDistilledReason": "Only the image-per-phase map and the \"concept, never the final vector\" rule were distilled. The narrative prompt recipes were NOT copied verbatim: they are bound to one brand pipeline, and morph-media carries the prompt STRUCTURE instead, which survives the model changing. Brand book, checklist and phases are the process of another skill. Review of 2026-09-29 (source c4c9164): the image-per-phase table and the \"concept, never the final vector\" rule are unchanged (the text now says \"image model\" instead of Gemini); what was added is morph-assets call mechanics (project_set, @brand, outputPath, models, variants, dryRun), tool-bound and kept out on purpose. Re-pinned only.",
113
113
  "standards": [],
114
114
  "skills": [
115
115
  "morph-media"
@@ -2848,7 +2848,7 @@
2848
2848
  "not-worth": "#o-que-não-merece",
2849
2849
  "size": "#tamanho"
2850
2850
  },
2851
- "digest": "Default is NO comment: code explains itself through names, types and tests. Before commenting, rename, extract a function or named constant, encode the state in the type, or write a test; comment only what none of these can express: an invariant the type does not express, an order that matters, an external quirk (third-party API, library bug with its link), or the contract of a package published for consumers outside the repo. Never process history (task, UC, ADR, FR, Gate, evaluator, spec.md, mockup: commit message and decisions.md), never restate the code, no XML doc/JSDoc on application members that repeats the signature, no file headers, section banners, #region, commented-out code or loose TODOs, no claims about behavior (make them test assertions). One line is normal, ~3 is the ceiling; more than one comment line per ten code lines means the names are not doing the work. The verify validators node (code-budget) warns on added comments that cite process and on added comment density.",
2851
+ "digest": "Default is NO comment: code explains itself through names, types and tests. Before commenting, rename, extract a function or named constant, encode the state in the type, or write a test; comment only what none of these can express: an invariant the type does not express, an order that matters, an external quirk (third-party API, library bug with its link), or the contract of a package published for consumers outside the repo. Never process history (task, UC, ADR, FR, Gate, evaluator, spec.md, mockup: commit message and decisions.md; in a test the link to the postcondition goes in the test NAME), never restate the code, no XML doc/JSDoc on application members that repeats the signature, no file headers, section banners, #region, commented-out code or loose TODOs, no claims about behavior (make them test assertions). One line is normal, ~3 is the ceiling; more than one comment line per ten code lines means the names are not doing the work. A heavily commented neighbour file is not a model. The comment-guard hook blocks an edit that ADDS process history, a banner/#region, a block over 3 lines or a doc-comment outside a published package; the verify validators node (code-budget) warns on added comments that cite process and on added comment density.",
2852
2852
  "verifiedAgainst": "C#, TypeScript e JavaScript (convenção de linguagem, sem pacote externo).",
2853
2853
  "provedBy": []
2854
2854
  }
@@ -43,7 +43,8 @@ Só o **porquê que o código não mostra**:
43
43
 
44
44
  - **História de processo.** Task, UC, ADR, FR, Gate, avaliador, `spec.md`, mockup, "confirmado ao
45
45
  implementar a T11", "o Gate 3 pediu". Isso vai na mensagem de commit e no `decisions.md`, onde tem
46
- data e autor e não envelhece dentro do arquivo.
46
+ data e autor e não envelhece dentro do arquivo. Em teste, o elo com a pós-condição vai no **nome**
47
+ do teste (`P5_estado_do_lead_espelha_o_crm`, `DisplayName`), nunca em `// UC-672 P5`.
47
48
  - **Repetição do código.** `// incrementa o contador` sobre `count++`, `// valida o request` sobre
48
49
  `validator.Validate(request)`. Nome bom substitui comentário.
49
50
  - **Doc-comment que repete a assinatura.** `/// <summary>Obtém o pedido.</summary>` sobre
@@ -63,7 +64,8 @@ Só o **porquê que o código não mostra**:
63
64
  Uma linha é o normal; teto de ~3. Acima disso o comentário justifica o volume (algoritmo não
64
65
  óbvio, armadilha de terceiro com a reprodução). Bloco longo narrando decisões é `decisions.md` com
65
66
  outro nome. Em volume, código de aplicação fica perto de zero: mais de uma linha de comentário a
66
- cada dez de código é sinal de que os nomes não estão fazendo o trabalho.
67
+ cada dez de código é sinal de que os nomes não estão fazendo o trabalho. Arquivo vizinho muito
68
+ comentado não é modelo: código novo segue este padrão, não a densidade do legado.
67
69
 
68
70
  ## Exemplo
69
71
 
@@ -90,6 +92,11 @@ await upsert({ key: doc.sourceId, hash: sha256(doc.body) });
90
92
 
91
93
  ## Na revisão
92
94
 
95
+ Na hora da edição, o hook `comment-guard` bloqueia o comentário **adicionado** que é história de
96
+ processo, banner/`#region`, bloco de mais de 3 linhas ou `///`/JSDoc em projeto que não é pacote
97
+ publicado. Comentário que o arquivo já tinha nunca é acusado. Repetir o código em uma linha não é
98
+ detectável por forma: fica com a revisão abaixo.
99
+
93
100
  Comentário que o código dispensava é achado da rubrica `code-quality` no Gate 3. O nó `validators`
94
101
  do `morph-spec verify` mede, nas linhas que a feature adicionou (validador `code-budget`), quantas
95
102
  linhas de comentário citam processo e a densidade de comentário sobre o código adicionado, e avisa
@@ -55,6 +55,8 @@ Give a start image and an end image, and let the model interpolate a camera trav
55
55
 
56
56
  **Avoid for scrub:** full orbits and any movement that returns to its origin. They become loops and kill the sense of progress.
57
57
 
58
+ **Long continuous sequence (hero → reveal → product).** Generate each next clip with `chainFrom: "<id of the previous clip>"` and its own `endImage`: the previous clip's real last frame becomes the start, so the seam matches (SSIM about 0.94 in the upstream skill's test). Join the clips with `media_tool { op: "concat" }` before `scrub_frames`. Source: the upstream `morph-lp-cinematic` skill; not re-measured here.
59
+
58
60
  ---
59
61
 
60
62
  ## Section 3: Faces Trip Moderation
@@ -41,6 +41,16 @@ ffmpeg -i source.mp4 -vf "fps=30,scale=1280:-1" -q:v 5 frames/frame_%03d.jpg
41
41
  | Numbering | `%03d` (frame_001) matches the 1-based `framePath` contract in Section 4. |
42
42
  | Format | JPG is the safe default (fast decode, universal). If the sequence gets heavy (tens of MB), also try WebP via `-c:v libwebp -q:v 70`, which usually cuts total weight at similar quality. |
43
43
 
44
+ **Frames from `media_tool scrub_frames` (morph-assets) do not follow the table above.** The tool writes `v{N}/frame_0001.webp …` (4-digit, 1-based, WebP by default) and a `frames.json` manifest (`version`, `count`, `format`, `width`, `height`, `fps`, `pattern`, `totalBytes`). Build `frameCount` and `framePath` from the manifest; a `%03d`/`.jpg` path from this section will not resolve against them. A new `v{N}` folder per extraction changes the URL by construction, so it is the cache-bust of Section 3:
45
+
46
+ ```js
47
+ import manifest from './hero-frames/frames.json'
48
+ const frameCount = manifest.count
49
+ const framePath = (i) => `/hero-frames/v${manifest.version}/frame_${String(i).padStart(4, '0')}.${manifest.format}`
50
+ ```
51
+
52
+ Source: the upstream `morph-lp-cinematic` skill (build-stack reference); not re-measured here.
53
+
44
54
  **Treat total sequence weight as a performance budget.** It shows up directly in Lighthouse. If it is too heavy, cut frame count (120 instead of 145) or resolution before cutting quality.
45
55
 
46
56
  **Mirror the footage when the subject collides with the copy.** If the subject sits on the side where text goes, flip it during extraction:
@@ -57,7 +67,7 @@ ffmpeg -i source.mp4 -vf "hflip,fps=30,scale=1664:-1" -q:v 5 frames/frame_%03d.j
57
67
 
58
68
  Re-extracting frames (for example going from 480p to 1080p) while keeping the same filenames means the browser serves the stale ones and nothing appears to change.
59
69
 
60
- Always version the frame URL and bump `N` on every re-extraction:
70
+ Always version the frame URL and bump `N` on every re-extraction (a `v{N}` folder per extraction, as `scrub_frames` writes, does it by construction):
61
71
 
62
72
  ```js
63
73
  const framePath = (i) => `/hero-frames/frame_${String(i).padStart(3, '0')}.jpg?v=2`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "8.41.0",
3
+ "version": "8.43.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -42,9 +42,13 @@ import {
42
42
  import { isSecondaryWorktree } from '../../../framework/hooks/shared/git-paths.js';
43
43
  import { buildComposeEnv } from '../../lib/e2e/compose-env.js';
44
44
  import { findWorktreeForFeature, resolvePrimaryRoot } from '../project/worktree.js';
45
+ import { createHeavyGate, HeavyBusyError } from '../../lib/heavy/gate.js';
46
+ import { afterHeavyRun } from '../../lib/heavy/cleanup.js';
47
+ import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
45
48
 
46
49
  /** Exit code for a usage error, matching `verify`'s convention. */
47
50
  const EXIT_USAGE = 2;
51
+ const EXIT_BUSY = 3;
48
52
 
49
53
  // ============================================================================
50
54
  // Shared context
@@ -158,13 +162,27 @@ export async function e2eUpCommand(featureName, options = {}) {
158
162
  process.exit(EXIT_USAGE);
159
163
  }
160
164
 
161
- const result = bringUpStack(ctx.cwd, {
162
- composeFile: ctx.composeFile,
163
- projectName: ctx.projectName,
164
- gitCommonDir: ctx.gitCommonDir,
165
- env: ctx.env,
166
- forceFresh: Boolean(options.fresh),
167
- });
165
+ const gate = createHeavyGate({ projectPath: ctx.cwd, label: 'e2e up', feature: featureName });
166
+ let result;
167
+ try {
168
+ gate.enter('e2e');
169
+ result = bringUpStack(ctx.cwd, {
170
+ composeFile: ctx.composeFile,
171
+ projectName: ctx.projectName,
172
+ gitCommonDir: ctx.gitCommonDir,
173
+ env: ctx.env,
174
+ forceFresh: Boolean(options.fresh),
175
+ });
176
+ } catch (err) {
177
+ if (!(err instanceof HeavyBusyError)) throw err;
178
+ if (options.json) {
179
+ console.log(JSON.stringify({ status: 'busy', feature: featureName, busy: { reason: err.reason, waitedMs: err.waitedMs } }, null, 2));
180
+ }
181
+ console.error(chalk.yellow(`\n⏳ e2e up não rodou: ${err.message}. Tente de novo quando liberar (\`morph-spec heavy status\`).\n`));
182
+ process.exit(EXIT_BUSY);
183
+ } finally {
184
+ gate.release();
185
+ }
168
186
 
169
187
  const auth =
170
188
  result.status === 'pass'
@@ -313,10 +331,11 @@ export async function e2eDownCommand(featureName, options = {}) {
313
331
  volumes: Boolean(options.volumes),
314
332
  });
315
333
  const stampDeleted = deleteStamp(ctx.gitCommonDir, ctx.projectName);
334
+ const cleanup = afterHeavyRun({ projectPath: ctx.cwd, config: readHeavyConfig(ctx.cwd), scope: 'e2e-down' });
316
335
 
317
336
  if (options.json) {
318
337
  console.log(JSON.stringify(
319
- { feature: featureName, project: ctx.projectName, tree: ctx.tree, status: result.status, stampDeleted },
338
+ { feature: featureName, project: ctx.projectName, tree: ctx.tree, status: result.status, stampDeleted, cleanup },
320
339
  null, 2,
321
340
  ));
322
341
  } else if (result.status === 'pass') {
@@ -100,13 +100,14 @@ export async function mcpSetupCommand(name, options) {
100
100
  const integrations = await detectIntegrations(targetPath);
101
101
  const claudeConfig = await detectClaudeConfig(targetPath);
102
102
  const existingMcps = claudeConfig.mcpServers || [];
103
+ const enabledPlugins = claudeConfig.enabledPlugins || [];
103
104
 
104
105
  if (options.list) {
105
- return showStatusList(targetPath, stack, existingMcps, integrations);
106
+ return showStatusList(targetPath, stack, existingMcps, integrations, enabledPlugins);
106
107
  }
107
108
 
108
109
  if (options.auto) {
109
- return autoInstall(targetPath, stack, existingMcps, integrations);
110
+ return autoInstall(targetPath, stack, existingMcps, integrations, enabledPlugins);
110
111
  }
111
112
 
112
113
  if (name) {
@@ -114,7 +115,7 @@ export async function mcpSetupCommand(name, options) {
114
115
  }
115
116
 
116
117
  // Interactive wizard
117
- return interactiveWizard(targetPath, stack, existingMcps, integrations);
118
+ return interactiveWizard(targetPath, stack, existingMcps, integrations, enabledPlugins);
118
119
  } catch (err) {
119
120
  console.error(chalk.red(`Error: ${err.message}`));
120
121
  process.exit(1);
@@ -124,8 +125,8 @@ export async function mcpSetupCommand(name, options) {
124
125
  /**
125
126
  * Show MCP status list (--list)
126
127
  */
127
- async function showStatusList(targetPath, stack, existingMcps, integrations) {
128
- const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { integrations });
128
+ async function showStatusList(targetPath, stack, existingMcps, integrations, enabledPlugins) {
129
+ const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { integrations, enabledPlugins });
129
130
  const statusRows = formatMcpStatusTable(orchestration, {});
130
131
 
131
132
  console.log(chalk.cyan('\n MCP Server Status\n'));
@@ -158,8 +159,8 @@ async function showStatusList(targetPath, stack, existingMcps, integrations) {
158
159
  /**
159
160
  * Auto-install credential-free MCPs (--auto)
160
161
  */
161
- async function autoInstall(targetPath, stack, existingMcps, integrations) {
162
- const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { autoOnly: true, integrations });
162
+ async function autoInstall(targetPath, stack, existingMcps, integrations, enabledPlugins) {
163
+ const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { autoOnly: true, integrations, enabledPlugins });
163
164
  const autoNames = Object.keys(orchestration.autoInstallable);
164
165
 
165
166
  if (autoNames.length === 0) {
@@ -419,8 +420,8 @@ async function setupSpecificMcp(targetPath, name, stack, existingMcps) {
419
420
  /**
420
421
  * Interactive wizard showing all MCPs
421
422
  */
422
- async function interactiveWizard(targetPath, stack, existingMcps, integrations) {
423
- const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { integrations });
423
+ async function interactiveWizard(targetPath, stack, existingMcps, integrations, enabledPlugins) {
424
+ const orchestration = orchestrateMcpSetup(targetPath, stack, existingMcps, { integrations, enabledPlugins });
424
425
 
425
426
  console.log(chalk.cyan('\n MCP Setup Wizard'));
426
427
  console.log(chalk.gray(` Stack: ${stack}`));