@polymorphism-tech/morph-spec 9.8.0 → 10.1.1

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 (202) hide show
  1. package/CLAUDE.md +12 -10
  2. package/README.md +4 -4
  3. package/bin/morph-spec.js +40 -43
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +28 -36
  6. package/docs/QUICKSTART.md +31 -191
  7. package/framework/CLAUDE.md +24 -30
  8. package/framework/MORPH.md +47 -123
  9. package/framework/agents.json +6 -374
  10. package/framework/commands/morph-apply.md +1 -1
  11. package/framework/commands/morph-archive.md +4 -3
  12. package/framework/commands/morph-hotfix.md +8 -30
  13. package/framework/commands/morph-infra.md +2 -2
  14. package/framework/commands/morph-media.md +10 -9
  15. package/framework/commands/morph-preflight.md +3 -3
  16. package/framework/commands/morph-review.md +15 -0
  17. package/framework/commands/morph-status.md +1 -1
  18. package/framework/docs/CLI.md +84 -95
  19. package/framework/evals/review-checklist.md +45 -53
  20. package/framework/hooks/README.md +4 -4
  21. package/framework/hooks/claude-code/core/README.md +1 -1
  22. package/framework/hooks/claude-code/core/denylist.json +4 -4
  23. package/framework/hooks/claude-code/core/gate-guard.js +9 -0
  24. package/framework/hooks/claude-code/core/masked-exit.js +49 -0
  25. package/framework/hooks/claude-code/core/state-sync.js +4 -1
  26. package/framework/hooks/claude-code/core/telemetry-log.js +1 -2
  27. package/framework/hooks/claude-code/core/test-budget.js +10 -70
  28. package/framework/hooks/claude-code/core/validate-completion.js +11 -24
  29. package/framework/hooks/claude-code/core/worktree-context.js +5 -22
  30. package/framework/hooks/claude-code/core/worktree-create.js +6 -45
  31. package/framework/hooks/claude-code/core/worktree-env-inject.js +2 -10
  32. package/framework/hooks/shared/e2e-check.js +0 -0
  33. package/framework/hooks/shared/gate-event.js +2 -2
  34. package/framework/hooks/shared/graph-refresh.js +20 -3
  35. package/framework/hooks/shared/heavy-command.js +32 -1
  36. package/framework/hooks/shared/heavy-config.js +1 -1
  37. package/framework/hooks/shared/next-step.js +63 -24
  38. package/framework/hooks/shared/phase-utils.js +6 -9
  39. package/framework/hooks/shared/report-progress.js +86 -0
  40. package/framework/hooks/shared/state-reader.js +3 -2
  41. package/framework/hooks/statusline.js +332 -0
  42. package/framework/mcp/registry.json +4 -4
  43. package/framework/phases.json +32 -10
  44. package/framework/rules/ai-agents-standards.md +10 -2
  45. package/framework/rules/csharp-standards.md +4 -0
  46. package/framework/rules/data-standards.md +1 -1
  47. package/framework/rules/ef-standards.md +20 -0
  48. package/framework/rules/frontend-standards.md +1 -1
  49. package/framework/rules/hangfire-standards.md +1 -1
  50. package/framework/rules/implement-report.md +25 -0
  51. package/framework/rules/infrastructure-standards.md +2 -0
  52. package/framework/rules/neon-auth-standards.md +1 -1
  53. package/framework/rules/neon-standards.md +3 -3
  54. package/framework/rules/nextjs-standards.md +7 -0
  55. package/framework/rules/plan-grammar.md +53 -0
  56. package/framework/rules/ui-design.md +27 -0
  57. package/framework/schemas/config.schema.json +11 -18
  58. package/framework/schemas/feature.schema.json +7 -1
  59. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  60. package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +1 -1
  61. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +2 -2
  62. package/framework/skills/core/morph-apply/SKILL.md +39 -166
  63. package/framework/skills/core/morph-apply/references/debugging.md +8 -0
  64. package/framework/skills/core/morph-apply/references/hotfix.md +42 -0
  65. package/framework/skills/core/morph-apply/references/machine.md +29 -0
  66. package/framework/skills/core/{morph-media/SKILL.md → morph-apply/references/media.md} +11 -74
  67. package/framework/skills/core/morph-apply/references/review.md +65 -0
  68. package/framework/skills/core/morph-plan/SKILL.md +49 -109
  69. package/framework/skills/core/morph-plan/references/critique.md +43 -0
  70. package/framework/skills/core/morph-plan/references/interview.md +37 -0
  71. package/framework/skills/core/morph-uiux/SKILL.md +47 -64
  72. package/framework/skills/core/morph-uiux/surfaces.json +32 -0
  73. package/framework/standards/STANDARDS.json +51 -28
  74. package/framework/standards/STANDARDS.meta.json +16 -19
  75. package/framework/standards/ai-agents/agent-session.md +2 -3
  76. package/framework/standards/ai-agents/context-providers.md +2 -2
  77. package/framework/standards/ai-agents/durable-workflows-hitl.md +2 -2
  78. package/framework/standards/ai-agents/providers/model-registry.md +2 -2
  79. package/framework/standards/ai-agents/rag-custom-pgvector.md +7 -6
  80. package/framework/standards/ai-agents/setup.md +1 -1
  81. package/framework/standards/ai-agents/tool-adapters.md +2 -2
  82. package/framework/standards/architecture/stack-baseline.md +70 -0
  83. package/framework/standards/backend/database/postgresql/database.md +22 -1
  84. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +33 -1
  85. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +2 -1
  86. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +12 -13
  87. package/framework/standards/frontend/design-system/ai-image-generation.md +51 -12
  88. package/framework/standards/frontend/design-system/ai-video-generation.md +50 -4
  89. package/framework/standards/frontend/design-system/art-direction.md +2 -2
  90. package/framework/standards/frontend/scroll-driven/frame-scrub.md +12 -2
  91. package/framework/standards/frontend/scroll-driven/scroll-components.md +76 -4
  92. package/framework/standards/infrastructure/docker/local-compose-isolation.md +2 -2
  93. package/framework/standards/infrastructure/neon/neon-pgvector.md +62 -20
  94. package/framework/standards/infrastructure/neon/neon-platform.md +61 -0
  95. package/framework/standards/infrastructure/neon/neon-setup.md +14 -13
  96. package/framework/standards/integration/mcp/mcp-tools.md +20 -20
  97. package/framework/templates/REGISTRY.json +1 -2
  98. package/framework/templates/context/ARCHITECTURE.md +1 -1
  99. package/framework/templates/docs/plan.md +15 -12
  100. package/framework/templates/docs/ui-mockups.md +17 -105
  101. package/framework/templates/infrastructure/docker/docker-compose.template.yml +1 -1
  102. package/package.json +1 -1
  103. package/src/commands/e2e/e2e.js +88 -23
  104. package/src/commands/graph/diff.js +74 -0
  105. package/src/commands/graph/status.js +1 -1
  106. package/src/commands/observability/cost.js +2 -4
  107. package/src/commands/observability/telemetry.js +1 -1
  108. package/src/commands/project/doctor.js +23 -39
  109. package/src/commands/project/fleet.js +5 -6
  110. package/src/commands/project/init.js +63 -36
  111. package/src/commands/project/stack-scan.js +3 -42
  112. package/src/commands/project/stack-set.js +26 -0
  113. package/src/commands/project/status.js +12 -13
  114. package/src/commands/project/update.js +39 -3
  115. package/src/commands/project/worktree.js +3 -14
  116. package/src/commands/state/advance.js +17 -17
  117. package/src/commands/state/approve.js +0 -6
  118. package/src/commands/state/archive.js +27 -1
  119. package/src/commands/state/auto-approval.js +2 -2
  120. package/src/commands/state/create.js +1 -6
  121. package/src/commands/state/gate-check.js +38 -31
  122. package/src/commands/state/gate-decision.js +3 -2
  123. package/src/commands/state/next.js +1 -0
  124. package/src/commands/state/recap.js +67 -0
  125. package/src/commands/state/report-check.js +36 -178
  126. package/src/commands/validation/plan-check.js +16 -11
  127. package/src/commands/validation/validate-feature.js +3 -3
  128. package/src/commands/validation/verify.js +3 -6
  129. package/src/core/paths/output-schema.js +3 -3
  130. package/src/core/state/state-manager.js +2 -4
  131. package/src/dashboard/app/components/CommandPalette.jsx +2 -5
  132. package/src/dashboard/app/components/GatePauses.jsx +116 -0
  133. package/src/dashboard/app/components/SessionMap.jsx +77 -0
  134. package/src/dashboard/app/pages/Activity.jsx +4 -0
  135. package/src/dashboard/app/pages/AgentMap.jsx +1 -2
  136. package/src/dashboard/app/pages/FeatureDetail.jsx +129 -186
  137. package/src/dashboard/app/pages/Features.jsx +7 -7
  138. package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
  139. package/src/dashboard/app/pages/Portfolio.jsx +40 -5
  140. package/src/dashboard/scanner/activity-scanner.js +29 -0
  141. package/src/dashboard/scanner/agents-scanner.js +11 -14
  142. package/src/dashboard/scanner/features-scanner.js +45 -24
  143. package/src/dashboard/scanner/project-api.js +11 -2
  144. package/src/dashboard/scanner/skills-scanner.js +11 -1
  145. package/src/lib/detectors/index.js +1 -1
  146. package/src/lib/e2e/applicability.js +3 -1
  147. package/src/lib/git/tree-state.js +1 -16
  148. package/src/lib/graph/diff-tree.js +127 -0
  149. package/src/lib/graph/graphify-runtime.js +12 -5
  150. package/src/lib/installers/memory-installer.js +3 -3
  151. package/src/lib/installers/rules-installer.js +57 -0
  152. package/src/lib/installers/stack-assets.js +17 -0
  153. package/src/lib/installers/stack-sync.js +219 -0
  154. package/src/lib/plan/checks.js +61 -109
  155. package/src/lib/plan/critique.js +28 -0
  156. package/src/lib/plan/media.js +42 -0
  157. package/src/lib/plan/plan-html.js +125 -0
  158. package/src/lib/plan/plan-parser.js +34 -45
  159. package/src/lib/recap/piper.js +36 -0
  160. package/src/lib/recap/recap.js +232 -0
  161. package/src/lib/review-reports.js +28 -4
  162. package/src/lib/risk-detector.js +90 -11
  163. package/src/lib/specs/screen-checklist.js +33 -48
  164. package/src/lib/stack/dotnet-build-ignores.js +8 -9
  165. package/src/lib/telemetry/metrics.js +27 -29
  166. package/src/lib/validators/reuse/reuse-validator.js +1 -9
  167. package/src/lib/validators/verify-runner.js +0 -7
  168. package/src/lib/worktree/fleet-scanner.js +7 -20
  169. package/src/lib/worktree/worktree-audit.js +0 -37
  170. package/src/scripts/setup-infra.js +15 -50
  171. package/src/utils/agents-installer.js +27 -201
  172. package/src/utils/hooks-installer.js +11 -95
  173. package/framework/hooks/claude-code/core/warn-bash-source-write.js +0 -23
  174. package/framework/hooks/claude-code/statusline.py +0 -699
  175. package/framework/hooks/claude-code/statusline.sh +0 -11
  176. package/framework/hooks/shared/agent-reap.js +0 -74
  177. package/framework/hooks/shared/slice-progress.js +0 -110
  178. package/framework/skills/core/morph-hotfix/SKILL.md +0 -133
  179. package/framework/skills/core/morph-machine/SKILL.md +0 -46
  180. package/framework/skills/core/morph-review/SKILL.md +0 -129
  181. package/framework/skills/core/morph-standards/SKILL.md +0 -52
  182. package/framework/standards/PROVENANCE.json +0 -119
  183. package/framework/standards/infrastructure/neon/neon-pgrag.md +0 -243
  184. package/src/commands/project/worktree-reap.js +0 -34
  185. package/src/commands/state/handoff.js +0 -65
  186. package/src/commands/validation/mutate.js +0 -263
  187. package/src/commands/validation/scout-check.js +0 -28
  188. package/src/lib/generators/handoff-generator.js +0 -172
  189. package/src/lib/mutation/adapters.js +0 -79
  190. package/src/lib/mutation/commands.js +0 -61
  191. package/src/lib/mutation/dotnet-introspection.js +0 -281
  192. package/src/lib/mutation/manifest.js +0 -144
  193. package/src/lib/mutation/plan.js +0 -44
  194. package/src/lib/mutation/report.js +0 -68
  195. package/src/lib/mutation/runner.js +0 -301
  196. package/src/lib/mutation/schemata.js +0 -60
  197. package/src/lib/mutation/snapshot.js +0 -65
  198. package/src/lib/mutation/subset.js +0 -109
  199. package/src/lib/mutation/trx.js +0 -50
  200. package/src/lib/scout/scout-check.js +0 -293
  201. package/src/lib/validators/traceability-check.js +0 -103
  202. package/src/lib/worktree/agent-reaper.js +0 -247
@@ -18,7 +18,7 @@ relatório começa igual e é append-only (vale o último `**Verdict:**`):
18
18
  - (o que melhoraria, sem bloquear)
19
19
  ```
20
20
 
21
- Sem bloqueador, escreva `Nenhum.` na seção. `SHIP` com bloqueador aberto é contradição e pausa o gate.
21
+ O `**Reviewed-SHA:**` é obrigatório: o `approve` recusa relatório sem ele ou de outro sha. Sem bloqueador, escreva `Nenhum.` na seção. `SHIP` com bloqueador aberto é contradição e pausa o gate.
22
22
  Confirmação depois de uma correção: seção nova ao fim com `**Verdict:**` e o `**Reviewed-SHA:**` novo —
23
23
  julgando só os seus bloqueadores, ou, sem eles, só o delta `{sha anterior}..{sha novo}`. Bloqueador
24
24
  que a correção fechou sai de `## Bloqueadores` (escreva `Nenhum.`) e o fechamento vai em `## Notas`;
@@ -31,70 +31,58 @@ diferente") é nota; nota não bloqueia e não pede rodada nova. Na dúvida, sem
31
31
  | Tipo | Quem | Quando | Evidência mínima |
32
32
  |------|------|--------|------------------|
33
33
  | `check` | verificador | cenário `Cn` NÃO PROVADO ou FALHA | o que falta provar, ou o repro que falha |
34
- | `teste` | verificador | teste vermelho; teste que não morde a regra que diz proteger | saída do verify, mutante sobrevivente, assert que passa pelo motivo errado |
34
+ | `teste` | verificador | teste vermelho; teste que não morde a regra que diz proteger; Cn de risco sem teste | saída do teste filtrado, assert que passa pelo motivo errado |
35
35
  | `regressao` | revisor | comportamento que funcionava e quebrou | consumidor afetado + o caminho que quebra |
36
- | `escopo` | revisor | o diff faz o que nenhuma fatia pediu, ou deixa de fazer o que o contrato pede | arquivo/trecho fora das fatias, ou o item do contrato ausente |
36
+ | `escopo` | revisor | o diff faz o que nenhum cenário pediu, ou deixa de fazer o que o contrato pede | trecho que nenhum Cn explica, ou o item do contrato ausente |
37
37
  | `arquitetura` | revisor | padrão ou abstração nova contra o stack e os standards; item da lista fechada de `architecture-engineering-lens#review-blockers` que o DIFF introduziu (o que já existia é nota) | o trecho e o standard que ele contraria |
38
38
  | `performance` | revisor | custo que o volume real torna visível | a consulta/lista e o volume que a derruba |
39
+ | `irreversivel` | revisor | o diff faz o que um revert não desfaz e nenhum `Cn` declara `degrau: migrar`/`irreversível`: apaga ou reescreve dado fora de migration, envia a terceiro, cobra, muda resposta de API ou webhook que alguém consome | o trecho e o efeito que fica depois do revert. Vai direto ao usuário, sem rodada de correção |
39
40
  | `seguranca` | segurança | authz, segredo, injeção, PII, custo sem teto, migration destrutiva | o caminho de exploração |
40
41
 
41
42
  ## Verificador (`4-review/verifier.md`)
42
43
 
43
- Missão: **provar cada cenário** do `2-plan/checks.json`. O default de todo `Cn` é NÃO PROVADO;
44
- critério que você não verificou nunca vira PROVADO por inferência. A prova é a que o plano declarou
45
- (`prova:` do Cn): `leitura` prova-se pelo `arquivo:linha` e pela fiação, **sem teste** — Cn `leitura`
46
- sem teste não é NÃO PROVADO, e pedir teste para ele não é bloqueador. Só Cn com `risco:` exige teste
47
- que morde.
44
+ Missão: **fazer cada cenário acontecer** e dizer se o resultado é o que o `2-plan/checks.json`
45
+ promete. Você recebe os cenários, a URL da app e o comando de reset — não o plano, o diff nem o relato
46
+ de quem implementou, de propósito: você prova o comportamento, não a versão dele que o implementador
47
+ conta. O default de todo `Cn` é NÃO PROVADO; o que você não executou (ou, em `leitura`, não leu)
48
+ nunca vira PROVADO por inferência.
48
49
 
49
50
  ```markdown
50
51
  ## Checks
51
52
 
52
53
  | Cn | status | evidência |
53
54
  |----|--------|-----------|
54
- | C1 | PROVADO | `src/Orders/Create.cs:42` + `OrdersTests.RecusaSemItens` (morde: M1 morto) |
55
- | C2 | NÃO PROVADO | nenhum teste exercita o caminho de escrita |
55
+ | C1 | PROVADO | `POST /orders {items:[]}` → 422 "pedido sem itens"; `OrdersTests.RecusaSemItens` verde |
56
+ | C2 | FALHA | /tenants?status=ativo lista 3 inativos (print `4-review/c2.png`) |
56
57
  ```
57
58
 
58
59
  Uma linha por cenário, status exato `PROVADO`, `NÃO PROVADO` ou `FALHA`. O gate pausa com cenário
59
- sem linha ou não PROVADO. Numa confirmação, uma `## Checks` nova só com os Cn que mudaram: vale a
60
- última linha de cada Cn. Para cada `Cn`, em ordem de dano:
61
-
62
- 1. **Onde vale**: `arquivo:linha` do código que entrega o cenário.
63
- 2. **Fiação**: existe caminho da entrada real ao efeito observável? A camada pura testada não prova
64
- que o handler a chama. Inventarie quem consome o eixo novo.
65
- 3. **O teste morde?** Nasceu verde e afirma o que já era verdade; só testa a recusa e nunca o caminho
66
- de escrita; guard cujos dois lados passam pelo mesmo serializador; asserção sobre texto que mede
67
- vocabulário, não sentido.
68
- 4. **Verificações mecânicas** (.NET minimal API / Next): todo serviço injetado numa lambda de endpoint
69
- tem registro em `Program.cs` (senão a app não sobe); prefixo de rota server-direct e de proxy são
70
- constantes distintas, cada uma usada só do seu lado.
71
- 5. **Runtime**: com o stack no ar (URL no dispatch), navegue o fluxo do cenário e confira console e
72
- network. Stack fora do ar é achado de infra, não defeito seu a provar.
73
-
74
- A suíte inteira é do orquestrador. Você não roda teste nem `verify` (o hook `test-budget` nega): a
75
- execução que prova mordida é a mutação abaixo; o resto, por leitura.
76
-
77
- **Prova por mutação — opcional, no máximo 3, só depois que revisor e segurança terminaram.** Só
78
- quando o diff tem uma linha de guarda ou regra que um cenário exige e você não achou o teste que a
79
- morde. Escreva `4-review/mutants.json` e rode `morph-spec mutate {feature}` **uma vez**:
80
-
81
- ```json
82
- {"mutants":[{"id":"M1","file":"src/x.cs","line":42,"find":"a && b","replace":"a",
83
- "expect":"OrdersTests.RecusaSemItens deve falhar: a regra de itens some"}]}
84
- ```
60
+ sem linha ou não PROVADO. Numa confirmação, uma `## Checks` nova só com os Cn pedidos: vale a última
61
+ linha de cada Cn.
62
+
63
+ **Como provar, pelo `prova:` do Cn:**
64
+
65
+ | `prova:` | Execução |
66
+ |---|---|
67
+ | `e2e` | Navegue o fluxo com o Playwright MCP na URL do dispatch, como o usuário faria; confira o resultado na tela, no console e no network. Rode `morph-spec e2e reset {feature}` **antes de cada fluxo**: estado de um fluxo nunca prova o seguinte. |
68
+ | `teste` | Ache o teste que cobre o Cn e rode **só ele**, pelo filtro do próprio runner (`dotnet test --filter <nome>`, `node --test <arquivo>`, `npx vitest run <arquivo> -t <nome>`, `npx playwright test <arquivo>`). `verify --filter` escolhe um nó inteiro da suíte: só serve quando o nó é esse teste. Verde não basta: leia o teste — ele morde? Nasceu afirmando o que já era verdade, só testa a recusa e nunca a escrita, ou o assert passa pelo motivo errado → bloqueador `teste`. Sem teste para um Cn com `risco:` → `teste`. |
69
+ | `eval` | Rode o nó de evals filtrado (`--filter <nó-evals> --skip-e2e --no-persist`) e compare a saída com o cenário. |
70
+ | `leitura` | `arquivo:linha` do código que entrega o cenário e a **fiação**: caminho da entrada real ao efeito (a camada pura não prova que o handler a chama). Sem teste, e pedir teste não é bloqueador. |
71
+ | `manual` | Reproduza o passo de `3-implement/evidencias/{Cn}.md` e diga se o resultado bate. |
72
+
73
+ Verificações mecânicas valem para todo Cn de backend (.NET minimal API / Next): serviço injetado numa
74
+ lambda de endpoint registrado em `Program.cs`; prefixo de rota server-direct e de proxy como
75
+ constantes distintas.
85
76
 
86
- `expect` é obrigatório: diga qual teste deve falhar e por quê **antes** de rodar — mutante cujo
87
- efeito você não sabe prever tende a ser equivalente. Barra invertida do código vira `{{BS}}`.
88
- Sobreviveu numa linha que um cenário exige → bloqueador `teste`. Sobreviveu em outra → nota.
89
- Sem subconjunto (exit 2) ou `inconclusive`: a mutação não roda a suíte inteira — prove por leitura
90
- (`arquivo:linha` + o teste que cobre), nunca em laço. Cole a
91
- seção `## Mutações` que a ferramenta imprime. A reversão é da ferramenta; rodada morta no meio:
92
- `morph-spec mutate --restore`.
77
+ Você não roda a suíte inteira nem `verify` sem filtro (o hook `test-budget` nega): ela já rodou antes
78
+ do dispatch. Stack fora do ar ou URL ausente: os `e2e` ficam NÃO PROVADO com o motivo — achado de
79
+ infra, não defeito seu a provar; não suba nem derrube o stack.
93
80
 
94
- **Feature que gera mídia** (alguma fatia cita em `standards:` `standards/ai-agents/modalities-image-gen.md`,
95
- `media-video.md` ou `media-pipeline.md`): grave `## Mídia gerada`, uma linha por afirmação — custo
96
- controlado, revisão humana, mídia marcada, hot path não bloqueado —, cada uma com o código que a prende
97
- e o teste que a prova. Afirmação falsa é bloqueador. Sem a seção, o gate pausa.
81
+ **Feature que gera mídia** (o `plan.md` cita um standard de mídia): grave `## Mídia gerada`, uma
82
+ linha por afirmação — custo controlado, revisão humana, mídia marcada, hot path não bloqueado —, cada
83
+ uma com o código que a prende e o teste que a prova. A régua de cada afirmação está em
84
+ `.morph/framework/standards/ai-agents/modalities-image-gen.md`, `ai-agents/media-video.md` e
85
+ `ai-agents/media-pipeline.md`. Afirmação falsa é bloqueador. Sem a seção, o gate pausa.
98
86
 
99
87
  **Prompt ou modelo alterado**: o nó `evals` do verify é a prova; sem ele, o gate pausa sozinho.
100
88
 
@@ -110,23 +98,27 @@ havendo item decidido, o gate pausa.
110
98
 
111
99
  ## Revisor (`4-review/reviewer.md`)
112
100
 
113
- Read-only, sobre o commit do dispatch: `git diff {base}..{sha}`, `git show {sha}:<caminho>`. Nunca a
114
- árvore de trabalho — o verificador pode estar com um mutante aplicado nela. Default SHIP.
101
+ Read-only, sobre o commit do dispatch: `git diff {base}..{sha}`, `git show {sha}:<caminho>`, e o
102
+ `2-plan/plan.md` (a spec). Nunca a árvore de trabalho: o commit é o que foi provado. Default SHIP.
115
103
 
116
- 1. **Escopo**: o diff contra as fatias (`arquivos:`) e o `## Contrato de implementação` do plano.
104
+ 1. **Escopo**: o diff contra os cenários e o `## Contrato de implementação` do plano.
117
105
  Teste além do orçamento também é escopo: teste para Cn `prova: leitura`, mais de um teste por Cn de
118
106
  risco sem porquê no plano, teste que só fixa texto/constante/DTO, o mesmo comportamento em duas
119
- camadas — bloqueador `escopo` (apague, não acrescente). Teste apagado sem porquê no relatório da
120
- fatia, edição de `.sln`/`.csproj`/glob do runner que tira testes da execução, ou `expectedCount` menor no `.morph/config/config.json` do diff sem teste apagado no mesmo
107
+ camadas — bloqueador `escopo` (apague, não acrescente). Teste apagado sem porquê nas `## Notas` do
108
+ `report.md`, edição de `.sln`/`.csproj`/glob do runner que tira testes da execução, ou `expectedCount` menor no `.morph/config/config.json` do diff sem teste apagado no mesmo
121
109
  diff, é bloqueador `regressao`: é suíte que parou de rodar, não limpeza.
122
110
  2. **Arquitetura**: padrão ou abstração nova contra o stack do projeto e os standards (camada que o
123
111
  projeto não usa, repositório sobre o DbContext, dependência nova sem decisão no plano).
124
- 3. **Regressão**: para cada símbolo público alterado, `morph-spec graph affected <símbolo> --prod --json`.
112
+ 3. **Regressão**: abra pela árvore do que mudou, `morph-spec graph diff --base {base} --json` (símbolos
113
+ tocados `+`/`~` e quem chama quem). Se ela recusar (`stale-graph`, `graph-drift`, `no-graph`), leia o
114
+ diff cru. Para cada símbolo público alterado, `morph-spec graph affected <símbolo> --prod --json`.
125
115
  Sem grafo (`no-graph`, `not-found`, `ambiguous`), caia para `git grep -n <símbolo> {sha}` e siga — a
126
116
  ausência de grafo nunca bloqueia, e ausência de consumidor nunca sai do grafo.
127
117
  4. **O que a correção abriu**: revisando a correção de uma rodada anterior, releia o comportamento
128
118
  vizinho ao que ela mudou.
129
119
  5. **Performance só com evidência**: lista sem paginação no servidor sobre volume real, N+1 visível.
120
+ **Irreversível não declarado**: o que fica depois de um `git revert` (dado apagado ou reescrito, mensagem
121
+ enviada, cobrança, contrato externo mudado) sem um `Cn` com `degrau:` que o cubra → bloqueador `irreversivel`.
130
122
  6. **Chamada a LLM**: dá para reconstruir o que foi ao provedor e o que ele respondeu neste turno?
131
123
  Não → nota.
132
124
  7. **Tela no diff**: cor ou espaçamento literal fora do CSS de tokens, mais de uma ação primária,
@@ -7,9 +7,9 @@ Comprehensive hooks system for enforcing spec-driven development at the Claude C
7
7
  ```
8
8
  framework/hooks/
9
9
  ├── claude-code/
10
- │ ├── core/ # every hook, plus chain.js (one process per multi-hook group)
11
- │ └── statusline.py # global statusline
10
+ │ └── core/ # every hook, plus chain.js (one process per multi-hook group)
12
11
  ├── shared/ # code hooks share (and src/ imports): state reader, guards, e2e, heavy, telemetry
12
+ ├── statusline.js # global statusline, not a hook: phase and scenarios through the project's .morph/framework/hooks/shared
13
13
  └── README.md
14
14
  ```
15
15
 
@@ -28,8 +28,7 @@ typechecks or runs tests: that is `morph-spec verify`.
28
28
  | **PreToolUse** (Bash) | conventional-commit.js | Block | Blocks a `git commit -m` whose subject is not Conventional Commits (`<type>(<scope>): <subject>`); a `$(…)`/heredoc message, `-F` and git-generated `Merge`/`Revert` pass |
29
29
  | **PreToolUse** (Bash) | block-destructive-worktree-removal.js | Block | Blocks `git worktree remove` / `rm -rf` / `Remove-Item -Recurse` on a directory holding reparse points (the infra junctions a raw removal would FOLLOW out, emptying the root's `.claude/`); names `morph-spec worktree remove` |
30
30
  | **PreToolUse** (Bash) | block-raw-worktree-add.js | Block | Blocks raw `git worktree add` (a tree with no junctions → no hooks, no port block, no identity, outside `worktrees/`); names `morph-spec worktree setup <feature>` / `worktree provision <name> --base <ref>`. `list`/`prune`/`remove`/`lock`/`move`/`repair` pass |
31
- | **PreToolUse** (Bash) | warn-bash-source-write.js | Warn | Heredoc/redirect/`tee` into project source (`isProjectSource`): context naming the file, the command still runs — Write/Edit is what phase, protected-file and comment guards see |
32
- | **WorktreeCreate** | worktree-create.js | Replaces native | Fires for `claude -w`, EnterWorktree and `Agent isolation:"worktree"`; replaces Claude Code's bare `git worktree add`. Runs `morph-spec worktree setup <n>` (known feature) or `worktree provision <n>` (task tree, branch `morph-task/<n>`), bases a task tree on the dispatcher's `morph/<f>` branch when `worktree.baseRef` is unset (`"head"` = current HEAD, `"fresh"` = default branch), prints the path as its last stdout line. CLI unreachable → plain git, still under `<root>/worktrees/` |
31
+ | **WorktreeCreate** | worktree-create.js | Replaces native | Fires for `claude -w`, EnterWorktree and `Agent isolation:"worktree"`; replaces Claude Code's bare `git worktree add`. Runs `morph-spec worktree setup <n>` (known feature) or `worktree provision <n>` (task tree, branch `morph-task/<n>`), bases a task tree on the default branch, prints the path as its last stdout line. CLI unreachable → plain git, still under `<root>/worktrees/` |
33
32
  | **WorktreeRemove** | worktree-remove.js | Fail-open | Routes Claude Code's own cleanup through `morph-spec worktree remove --target --if-clean` (junctions dropped first; a dirty tree is left alone). Always exit 0. Secondary net — the event was observed NOT to fire headless; the Bash guard above stays the primary defense |
34
33
  | **PostToolUse** (Write\|Edit) | comment-guard.js | Blocking | Blocks when the edit ADDED a comment the `architecture-code-comments` standard never allows: process history (ADR, scenario/slice id, gate, plan.md), banner/`#region`, a block over 3 lines, or a doc-comment outside a published package. Old comments are never reported; last link of the chain |
35
34
  | **PostToolUse** (Write\|Edit\|AskUserQuestion) | state-sync.js | State sync | Autonomous state updates (feature register, output tracking, gate approval) |
@@ -37,6 +36,7 @@ typechecks or runs tests: that is `morph-spec verify`.
37
36
  | **PostToolUse** (all tools) | telemetry-log.js | Telemetry | Appends every tool call (skill, sub-agent dispatch + prompt, knowledge-base read, bash/edit) to `.morph/logs/events.jsonl`; silent, fail-open — the NLH execution-path capture read back by `morph-spec telemetry` |
38
37
  | **PostToolUse** (all tools) | loop-detect.js | Advisory | Warns when the last 8 actions collapse to ≤2 unique signatures |
39
38
  | **PostToolUse** (all tools) | graph-refresh-kick.js | Background | After a Bash or PowerShell command that runs git/gh/morph-spec, launches the DETACHED refresh of the ROOT's codebase graph (`shared/graph-refresh.js`: cwd in the git common dir, never a worktree; one lock per repository; staleness by `shared/graph-freshness.js` — stamp plus the per-tree run record in `<git-common-dir>/morph-spec/graph-refresh-state.json`); never waits, never injects |
39
+ | **PostToolUse** (all tools) | masked-exit.js | Advisory | After a Bash test/build/verify run (`testRunKind`/`morphVerifyCall`/`heavyKind` of the producer) whose exit `exitCodeMasked` hides (`\| tail`, `; echo`, `\|\| true`, `&`), names the form and says to write the output to `.morph/logs/test-last.log` and read it; silent when the command prints `$?`/`PIPESTATUS` |
40
40
  | **PostToolUseFailure** (all tools) | telemetry-log.js | Telemetry | The failed call (a Bash that exits non-zero fires only this event): exit code parsed from the `Exit code N` line, outcome and duration — never the error body |
41
41
  | **PostToolBatch** | telemetry-log.js | Telemetry | One `ToolBatch` event per model turn that used tools (the turn counter of `cost --metrics`) |
42
42
  | **SubagentStop** | telemetry-log.js | Telemetry | `AgentUsage`: the sub-agent's token usage read from its own transcript, as a delta per agent; never blocks the stop |
@@ -4,4 +4,4 @@ See `framework/MORPH.md` for full documentation.
4
4
 
5
5
  ## test-budget
6
6
 
7
- `PreToolUse` Bash (antes do `heavy-gate`), `PostToolUse` e `PostToolUseFailure`. Só age em sub-agent (`agent_id`) com `.morph/` presente. Nega: `morph-spec verify` sem `--filter`; a 4ª rodada de teste do mesmo sub-agent (rodada = `testRunKind` ou `verify --filter`; exit 3 não conta), mandando gravar `PARCIAL` e devolver; todo teste ou `verify` de `morph-verifier`/`morph-reviewer`/`morph-security` (`morph-spec mutate` passa). Contador em `.morph/memory/test-runs/<agent_id>.json`; o `SendMessage` do orquestrador para o agente (a retomada depois do `PARCIAL`) zera o dele. Fail-open.
7
+ `PreToolUse` Bash (antes do `heavy-gate`). Só age nos agentes do Gate 3 (`agent_id` com `agent_type` em `REVIEW_AGENT_TYPES`: `morph-verifier`/`morph-reviewer`/`morph-security`) com `.morph/` presente. Nega `morph-spec verify` sem `--filter` e a suíte de teste inteira; libera o teste filtrado (`testRunCall` de `framework/hooks/shared/heavy-command.js` decide o que é filtro). A sessão principal e os demais sub-agents não são tocados. Sem contador. Fail-open.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.3.0",
2
+ "version": "1.4.0",
3
3
  "patterns": [
4
4
  "DROP TABLE",
5
5
  "DROP DATABASE",
@@ -76,7 +76,7 @@
76
76
  ],
77
77
  "subagentOnly": true,
78
78
  "display": "git add -A",
79
- "reason": "leva junto o arquivo de outra fatia na mesma árvore; `git add <paths>` só dos seus"
79
+ "reason": "altera a árvore que o Gate 3 está lendo"
80
80
  },
81
81
  {
82
82
  "executable": "git",
@@ -85,7 +85,7 @@
85
85
  ],
86
86
  "subagentOnly": true,
87
87
  "display": "git stash",
88
- "reason": "mexe na árvore que outras fatias e o verificador estão usando"
88
+ "reason": "altera a árvore que o Gate 3 está lendo"
89
89
  },
90
90
  {
91
91
  "executable": "git",
@@ -100,7 +100,7 @@
100
100
  ],
101
101
  "subagentOnly": true,
102
102
  "display": "git checkout --",
103
- "reason": "reverte código de produção; a prova de que o teste morde é do verificador"
103
+ "reason": "altera a árvore que o Gate 3 está lendo"
104
104
  }
105
105
  ]
106
106
  }
@@ -70,6 +70,15 @@ function checkAskUserQuestion(input, sessionRoot) {
70
70
  'PostToolUse'
71
71
  );
72
72
  }
73
+ if (decision.resolver === 'session') {
74
+ return contextResult(
75
+ `⚠️ [morph-spec] GATE ASKED TOO EARLY — o Gate "${gate}" pausou com resolver:"session" ` +
76
+ `(${decision.reason || 'sinal mecânico'}): essa pausa não é do humano. ` +
77
+ `Faça o que a razão diz e rode \`morph-spec gate-check ${feature} ${gate}\` de novo; ` +
78
+ `a pergunta só vem quando o gate-check devolver resolver:"human".`,
79
+ 'PostToolUse'
80
+ );
81
+ }
73
82
  return null;
74
83
  }
75
84
  return null;
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { stateExists } from '../../shared/state-reader.js';
4
+ import { contextResult, runStandalone } from '../../shared/hook-response.js';
5
+ import { exitCodeMasked, hasUnquotedPipe, producerOf } from '../../shared/command-tokens.js';
6
+ import { heavyKind, morphVerifyCall, testRunKind } from '../../shared/heavy-command.js';
7
+
8
+ // Printing `$?` or PIPESTATUS puts the real code in the output, so the session already sees it.
9
+ const EXIT_SHOWN = /\$\?|PIPESTATUS/;
10
+
11
+ function runKind(producer) {
12
+ if (morphVerifyCall(producer)) return 'morph-spec verify';
13
+ return testRunKind(producer) ?? heavyKind(producer);
14
+ }
15
+
16
+ function maskForm(command) {
17
+ const bare = command.replace(/"[^"]*"|'[^']*'/g, '""');
18
+ if (hasUnquotedPipe(command) && !/\bpipefail\b/.test(command)) {
19
+ const filter = /(?<![|>])\|(?!\|)\s*([^\s|;&]+)/.exec(bare)?.[1];
20
+ return filter ? `| ${filter}` : '|';
21
+ }
22
+ const or = /\|\|\s*([^\s;&|]+)/.exec(bare);
23
+ if (or) return `|| ${or[1]}`;
24
+ const reporter = /;\s*(echo|printf|true|:|exit\s+0)\b/.exec(bare);
25
+ if (reporter) return `; ${reporter[1]}`;
26
+ if (/(?<![&>])&(?![&>])/.test(bare)) return '&';
27
+ return 'o comando que vem depois';
28
+ }
29
+
30
+ export async function run(payload) {
31
+ try {
32
+ if (payload?.tool_name !== 'Bash') return null;
33
+ const command = payload.tool_input?.command;
34
+ if (typeof command !== 'string' || !command.trim()) return null;
35
+ if (!stateExists(payload.cwd || undefined)) return null;
36
+ if (!exitCodeMasked(command) || EXIT_SHOWN.test(command)) return null;
37
+ const kind = runKind(producerOf(command));
38
+ if (!kind) return null;
39
+ return contextResult(
40
+ `⚠️ [morph-spec] O exit de \`${kind}\` está mascarado por \`${maskForm(command)}\`: o código que você viu é o do último comando, não o do teste/build. `
41
+ + 'Grave a saída em arquivo (`… > .morph/logs/test-last.log 2>&1`) e leia o arquivo.',
42
+ 'PostToolUse'
43
+ );
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ await runStandalone(run);
@@ -164,8 +164,11 @@ function handleAskUserQuestion(input, sessionRoot) {
164
164
  }
165
165
 
166
166
  // Same contract as `approve --self-assessed`: approving review without its reports is stamped, never silent.
167
+ // The reports live in the tree that ran the review, which may not be the one the feature was found in.
168
+ const trees = allWorkTrees(sessionRoot);
169
+ const reportIn = (f) => trees.some((t) => existsSync(join(t, '.morph', 'features', featureName, '4-review', f)));
167
170
  const selfAssessed = gate === 'review' && feature?.workType !== 'hotfix'
168
- && (feature?.workType === 'chore' ? ['reviewer.md'] : ['verifier.md', 'reviewer.md']).some((f) => !existsSync(join(root, '.morph', 'features', featureName, '4-review', f)));
171
+ && (feature?.workType === 'chore' ? ['reviewer.md'] : ['verifier.md', 'reviewer.md']).some((f) => !reportIn(f));
169
172
  if (selfAssessed) {
170
173
  warnings.push(
171
174
  `⚠️ [morph-spec] Gate 3 de "${featureName}" aprovado SEM os relatórios de review independentes (verifier.md, reviewer.md) — ` +
@@ -46,8 +46,7 @@ function classifyReadKind(target) {
46
46
  if (inFeature) {
47
47
  const rel = inFeature[1];
48
48
  const PLAN_ARTIFACT_BASENAMES = new Set([
49
- 'plan.md', 'checks.json',
50
- 'handoff.md',
49
+ 'plan.md', 'checks.json', 'report.md',
51
50
  ]);
52
51
  const base = rel.split('/').pop();
53
52
  if (PLAN_ARTIFACT_BASENAMES.has(base) || rel.includes('reports/')) {
@@ -1,87 +1,27 @@
1
1
  #!/usr/bin/env node
2
- import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
- import { join } from 'node:path';
4
2
  import { blockResult, runStandalone } from '../../shared/hook-response.js';
5
3
  import { stateExists } from '../../shared/state-reader.js';
6
- import { QUEUE_FULL_EXIT, REVIEW_AGENT_TYPES, morphVerifyCall, testRunKind } from '../../shared/heavy-command.js';
7
-
8
- export const TEST_RUN_BUDGET = 3;
4
+ import { REVIEW_AGENT_TYPES, morphVerifyCall, testRunCall } from '../../shared/heavy-command.js';
9
5
 
10
6
  export const REASONS = {
11
- verify: '`morph-spec verify` cheio é do orquestrador, depois do seu retorno. Rode só o filtro dos seus testes.',
12
- budget: '4ª rodada de teste nesta fatia. Grave `PARCIAL` na 1ª linha do relatório com o que ainda falha e devolva: o orquestrador decide.',
13
- review: 'Agente de review não roda teste: a prova é o verify do orquestrador. Para provar que um teste morde, `morph-spec mutate`.',
7
+ verify: '`morph-spec verify` sem `--filter` é a prova do gate, rodada pela sessão. Rode só o nó do cenário: `morph-spec verify <f> --filter <nó> --no-persist`.',
8
+ suite: 'Suíte inteira não: rode só o teste do cenário que você está provando (o arquivo, `-t <nome>` ou `--filter <expr>`).',
14
9
  };
15
10
 
16
- // Pre and Post must land on the same counter: a sub-agent's cwd drifts into subfolders.
17
11
  const projectRoot = (payload, env) => env.CLAUDE_PROJECT_DIR || payload?.cwd || process.cwd();
18
12
 
19
- const counterPath = (root, agentId) =>
20
- join(root, '.morph', 'memory', 'test-runs', `${String(agentId).replace(/[^A-Za-z0-9_-]/g, '_')}.json`);
21
-
22
- function readCounter(path) {
23
- try {
24
- return JSON.parse(readFileSync(path, 'utf-8')) ?? {};
25
- } catch {
26
- return {};
27
- }
28
- }
29
-
30
- const readRuns = (path) => Number(readCounter(path).runs) || 0;
31
-
32
- function recordRun(path, name) {
33
- mkdirSync(join(path, '..'), { recursive: true });
34
- const tmp = `${path}.tmp.${process.pid}`;
35
- writeFileSync(tmp, JSON.stringify({ runs: readRuns(path) + 1, ...(name ? { name } : {}) }));
36
- renameSync(tmp, path);
37
- }
38
-
39
- function exitCodeOf(payload) {
40
- const code = /^exit code (\d+)/i.exec(String(payload.error ?? '').split('\n', 1)[0]);
41
- return code ? Number(code[1]) : null;
42
- }
43
-
44
- // The orchestrator resuming an agent is the decision the budget hands back to it: a fresh budget.
45
- function resetOnResume(payload, env) {
46
- const to = payload.tool_input?.to;
47
- if (payload.agent_id || payload.hook_event_name !== 'PostToolUse' || !to) return;
48
- const root = projectRoot(payload, env);
49
- if (!stateExists(root)) return;
50
- // `to` may be the dispatch name instead of the id: the counter records the name it ran under.
51
- const byId = counterPath(root, to);
52
- const dir = join(byId, '..');
53
- for (const file of readdirSync(dir)) {
54
- const path = join(dir, file);
55
- if (path === byId || readCounter(path).name === to) rmSync(path, { force: true });
56
- }
57
- }
58
-
59
13
  export async function run(payload, deps = {}) {
60
14
  try {
61
- if (payload?.tool_name === 'SendMessage') {
62
- resetOnResume(payload, deps.env ?? process.env);
63
- return null;
64
- }
65
- if (payload?.tool_name !== 'Bash' || !payload.agent_id) return null;
15
+ if (payload?.hook_event_name !== 'PreToolUse' || payload.tool_name !== 'Bash') return null;
16
+ if (!payload.agent_id || !REVIEW_AGENT_TYPES.has(payload.agent_type)) return null;
66
17
  const command = payload.tool_input?.command || '';
67
- const kind = testRunKind(command);
68
18
  const verify = morphVerifyCall(command);
69
- if (!kind && !verify) return null;
70
-
71
- const env = deps.env ?? process.env;
72
- const root = projectRoot(payload, env);
73
- if (!stateExists(root)) return null;
74
- const path = counterPath(root, payload.agent_id);
75
-
76
- if (payload.hook_event_name === 'PreToolUse') {
77
- if (REVIEW_AGENT_TYPES.has(payload.agent_type)) return blockResult(REASONS.review);
78
- if (verify && !verify.filtered) return blockResult(REASONS.verify);
79
- if (readRuns(path) >= TEST_RUN_BUDGET) return blockResult(REASONS.budget);
80
- return null;
81
- }
19
+ const tests = testRunCall(command);
20
+ if (!verify && !tests) return null;
21
+ if (!stateExists(projectRoot(payload, deps.env ?? process.env))) return null;
82
22
 
83
- if (payload.hook_event_name === 'PostToolUseFailure' && exitCodeOf(payload) === QUEUE_FULL_EXIT) return null;
84
- if (payload.hook_event_name === 'PostToolUse' || payload.hook_event_name === 'PostToolUseFailure') recordRun(path, payload.agent_type);
23
+ if (verify && !verify.filtered) return blockResult(REASONS.verify);
24
+ if (tests && !tests.filtered) return blockResult(REASONS.suite);
85
25
  } catch {}
86
26
  return null;
87
27
  }
@@ -9,30 +9,17 @@ import {
9
9
  import { contextResult, runStandalone } from '../../shared/hook-response.js';
10
10
  import { appendTelemetryEvent } from '../../shared/telemetry-logger.js';
11
11
  import { readTranscriptUsage, usageDelta } from '../../shared/transcript-usage.js';
12
- import { sliceProgress, sliceReportPath, inspectReport } from '../../shared/slice-progress.js';
13
-
14
- // Advisory: the same rule `report-check` applies when the orchestrator resumes control.
15
- function auditSliceReports(featurePath) {
16
- const progress = sliceProgress(featurePath);
17
- if (!progress) return [];
18
- const warnings = [];
19
- for (const slice of progress.slices) {
20
- if (slice.status !== 'feito') continue;
21
- const { missing } = inspectReport(sliceReportPath(featurePath, slice.id), { covers: slice.covers });
22
- if (missing.length) {
23
- const hints = missing.map((id) => MISSING_HINT[id] ?? `cite ${id} pelo id`);
24
- warnings.push(`relatório de ${slice.id} sem: ${missing.join(', ')} — ${hints.join('; ')} (formato na skill morph-apply; morph-spec report-check)`);
25
- }
26
- }
27
- return warnings;
12
+ import { implementProgress } from '../../shared/report-progress.js';
13
+
14
+ function auditReport(featurePath) {
15
+ const progress = implementProgress(featurePath);
16
+ if (!progress || progress.legacy) return [];
17
+ const bare = progress.checks.filter((c) => c.status === 'feito' && !c.evidence.trim()).map((c) => c.id);
18
+ return bare.length
19
+ ? [`report.md: ${bare.join(', ')} feito sem evidência — o teste, a linha de código ou o passo que prova (morph-spec report-check)`]
20
+ : [];
28
21
  }
29
22
 
30
- const MISSING_HINT = {
31
- what: '2-3 frases do que foi feito e como foi provado',
32
- files: 'linha **Arquivos:** com todo arquivo criado ou modificado',
33
- partial: '1ª linha PARCIAL: a fatia não terminou',
34
- };
35
-
36
23
  const COMPACT_TRIGGER_PHASES = new Set(['implement']);
37
24
 
38
25
  const HEAVY_SOURCE_PHASES = new Set(['plan', 'uiux']);
@@ -89,7 +76,7 @@ export async function run(payload) {
89
76
  // Hash-gated: the audit re-runs every Stop, so only a changed warning set is injected; resolving clears the hash.
90
77
  try {
91
78
  const featurePath = join(cwd, '.morph', 'features', name);
92
- const reportWarnings = auditSliceReports(featurePath);
79
+ const reportWarnings = auditReport(featurePath);
93
80
 
94
81
  const warnedHashesPath = join(memDir, 'warned-hashes.json');
95
82
  let warnedHashes = {};
@@ -100,7 +87,7 @@ export async function run(payload) {
100
87
  if (reportWarnings.length > 0) {
101
88
  const hash = createHash('sha1').update(reportWarnings.join('\n')).digest('hex');
102
89
  if (warnedHashes[name]?.reports !== hash) {
103
- warnings.push('📋 Relatórios de fatia:');
90
+ warnings.push('📋 3-implement/report.md:');
104
91
  for (const w of reportWarnings) warnings.push(` • ${w}`);
105
92
  warnedHashes[name] = { ...warnedHashes[name], reports: hash };
106
93
  try { writeFileSync(warnedHashesPath, JSON.stringify(warnedHashes, null, 2), 'utf-8'); } catch {}
@@ -9,13 +9,12 @@ import {
9
9
  getFeaturePhase,
10
10
  getPendingGates,
11
11
  } from '../../shared/state-reader.js';
12
- import { sliceProgress } from '../../shared/slice-progress.js';
12
+ import { implementProgress } from '../../shared/report-progress.js';
13
13
  import { collectNextSignals, decideNext, formatNext } from '../../shared/next-step.js';
14
14
  import { contextResult, runStandalone } from '../../shared/hook-response.js';
15
15
  import { acquireClaim, foreignClaim, claimAge } from '../../shared/claim-registry.js';
16
16
  import { rotateEventsLogIfLarge } from '../../shared/telemetry-logger.js';
17
17
  import { kickGraphRefresh, seedFromPrimary } from '../../shared/graph-refresh.js';
18
- import { kickAgentReap } from '../../shared/agent-reap.js';
19
18
  import { isInContainer } from '../../shared/worktree-root.js';
20
19
  import { isSecondaryWorktree, resolveGitCommonDir } from '../../shared/git-paths.js';
21
20
 
@@ -121,18 +120,6 @@ function buildEnvLine(dir) {
121
120
  }
122
121
  }
123
122
 
124
- function buildHandoffLine(dir, feature) {
125
- try {
126
- const handoffPath = join(dir, '.morph', 'features', feature, 'handoff.md');
127
- if (!existsSync(handoffPath)) return null;
128
- const generatedAt = readFileSync(handoffPath, 'utf8')
129
- .match(/gerado em (\d{4}-\d{2}-\d{2}T[\d:.]+Z)/)?.[1];
130
- return `📄 Handoff: .morph/features/${feature}/handoff.md${generatedAt ? ` (gerado ${generatedAt})` : ''} — leia antes de continuar.`;
131
- } catch {
132
- return null;
133
- }
134
- }
135
-
136
123
  // No git here, so verify staleness is unknown: an escalation the tree already moved past, or the
137
124
  // root skeleton of a feature living in a worktree, would be an order read from stale facts.
138
125
  function buildNextLine(dir, feature) {
@@ -161,9 +148,6 @@ function buildWorktreeContext(dir, feature) {
161
148
  const envLine = buildEnvLine(dir);
162
149
  if (envLine) text += `\n${envLine}`;
163
150
 
164
- const handoffLine = buildHandoffLine(dir, feature);
165
- if (handoffLine) text += `\n${handoffLine}`;
166
-
167
151
  text += buildMissingInfraWarning(dir);
168
152
  return text;
169
153
  }
@@ -185,11 +169,11 @@ function buildActiveFeatureSummary(dir) {
185
169
  if (!active) return null;
186
170
 
187
171
  const phase = getFeaturePhase(active.name, dir);
188
- const slices = sliceProgress(join(dir, '.morph', 'features', active.name));
172
+ const progress = implementProgress(join(dir, '.morph', 'features', active.name));
189
173
  const pending = getPendingGates(active.name, dir);
190
174
 
191
175
  let text = `MORPH-SPEC: feature ativa ${active.name} (fase ${phase})`;
192
- if (slices) text += ` · fatias ${slices.done}/${slices.total}`;
176
+ if (progress) text += ` · cenários ${progress.done}/${progress.total}`;
193
177
  if (pending.length) {
194
178
  text += ` · gates pendentes: ${pending.join(', ')}`;
195
179
  }
@@ -207,8 +191,8 @@ function claimBlock(dir, feature, sessionId) {
207
191
  if (!other) return '';
208
192
  return `\n⚠️ ATENÇÃO — a feature ${feature} tem claim VIVO de OUTRA sessão`
209
193
  + `\n pid ${other.pid}${other.path ? ` · ${other.path}` : ''} · visto há ${claimAge(other)}`
210
- + '\n Duas sessões na mesma feature já custaram um relatório de fatia inteiro e um sub-agent'
211
- + '\n (o worktree sumiu debaixo dele). Confirme com a outra sessão ANTES de agir —'
194
+ + '\n Duas sessões na mesma feature sobrescrevem o trabalho uma da outra.'
195
+ + '\n Confirme com a outra sessão ANTES de agir —'
212
196
  + '\n principalmente antes de `finish`, `archive` ou `delete`.'
213
197
  + `\n Se aquela sessão morreu: morph-spec claim ${feature} --release`;
214
198
  } catch {
@@ -229,7 +213,6 @@ export async function run(payload) {
229
213
  // Only the ROOT's graph is refreshed in background: a detached process holding a worktree directory made `finish` fail on win32.
230
214
  try { seedFromPrimary(dir); } catch {}
231
215
  try { kickGraphRefresh(dir); } catch {}
232
- try { kickAgentReap(dir); } catch {}
233
216
 
234
217
  if (isSecondaryWorktree(dir)) {
235
218
  const task = taskFromBranch(dir);