@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
package/CLAUDE.md CHANGED
@@ -106,6 +106,7 @@ State lives in `.morph/state.json`. Phase derived from filesystem (not stored).
106
106
  - **Output type normalization** — `normalizeOutputType()` auto-converts kebab-case to camelCase
107
107
  - **Template helpers** — Handlebars v2.0: pascalCase, camelCase, snakeCase, kebabCase, pluralize, plus comparison/logic helpers
108
108
  - **CLI structure** — Commands are thin wrappers; business logic in `src/core/` or `src/lib/`
109
+ - **Comentários** — o padrão é nenhum: nome, tipo e teste explicam o código. Só o porquê que o código não mostra (invariante, ordem que importa, quirk externo com link), em até 3 linhas. Sem história de processo (issue, PR, feature, "medido na 8.x") no código: isso vai no commit e na memória. JSDoc só em export que outro módulo consome e cujo contrato o nome não diz. **Não imite a densidade dos arquivos existentes**: boa parte do código legado deste repo é mais comentada do que esta regra. O hook `comment-guard` roda aqui também (`.claude/settings.json`)
109
110
 
110
111
  ## Framework Development Guardrails
111
112
 
@@ -147,8 +148,9 @@ When modifying framework components, verify the full scope of related files.
147
148
  | `src/lib/tasks/test-runner.js` | `verify-runner.js` (o nó `tests` repassa o `--timeout` e lê `timedOut`), `src/commands/project/worktree.js` (as constantes `WORKTREES_DIR`/`LEGACY_WORKTREES_DIR` que a descoberta ignora — duplicar as strings aqui é como o bug volta), `test/lib/test-runner.test.js` (o canário do ignore de worktrees e o do timeout escalado). Um worktree é o checkout de OUTRA feature: varrê-lo dobra o relógio e reporta sobre código fora da árvore avaliada. `stripAnsi` roda antes de TODO extrator de contagem (ponto único em `parseTestCount`) e das strings de `isZeroTestRun` — uma cor SGR no meio do sumário derrotava os regexes ancorados (#110). O extrator `dotnet` lê os DOIS sumários (#115): a linha VSTest e o bloco MTP (cabeçalho `Test run summary:`/`Resumo da execução de teste:` na coluna 0, `total:` indentado), em pt-BR e en-US — UM bloco por invocação de `dotnet test` (uma solução de dois projetos dá um bloco agregado, medido), então somar blocos é somar links `&&`; um terceiro locale é `null`. `mtpSummaryTotals` (um total por bloco) é o que `judgeNarrowedRun` lê para achar, numa cadeia MTP recortada, o link que rodou 0 (exit 8 ignorado) escondido pela soma. `ZERO_RUN_SIGNATURES` mistura o medido e o documentado — medidos (capturas em `test/fixtures/test-output/`): o exit 8 do MTP e a string pt-BR do VSTest; só documentados, sem captura: a string en-US do VSTest e as de vitest/jest. O exit 9 fica FORA de propósito: é piso de projeto, não zero testes. Leitor: `judgeNarrowedRun` (`verify-runner.js`). `extractCsprojTargets`, `isInside` e `canonicalPath` também são lidos por `test-scope.js` (`canonicalPath` dá a todo path de `outputs` a grafia do DISCO — no win32 um `Foo.test.ts` mal grafado passa no `isFile`, e sob `--linux`, case-sensitive, o runner não o acha enquanto um irmão mantém a contagem acima de zero); `dotnet-test-introspection.js` lê só `isInside`. Um segundo canonicalizador é como os dois lados passam a discordar sobre o mesmo path |
148
149
  | `src/lib/specs/screen-checklist.js` (checklist de tela, #129) | o sinal mora aqui (`collectScreenChecklist`) e tem QUATRO leitores: `gate-check.js` na `uiux` (`problems` → pausa) e no `review` (`uncovered`: item decidido sem linha na seção `## Checklist de tela` que `evaluator-report.js` lê — última ocorrência; tela toda "não se aplica" não deve nada), e `approve.js`/`advance.js` com assinatura `auto` (`uiuxAutoApprovalBlock`). Política só em `risk-detector.js`: `uiuxChecklistPause` é o predicado ÚNICO do gate-check pausar e do auto recusar — uma segunda regra no advance fez approve e advance discordarem da mesma feature — e `gateDecisions.uiux.pause === false` destrava os dois só para o mockup que a decisão viu: `persistGateDecision` (`gate-decision.js`, o escritor único de `gateDecisions`) grava `mockupsHash` e `uiuxAutoApprovalBlock` o compara com o arquivo atual — os dois leem `mockupsHash` daqui, e um segundo cálculo do hash (outra normalização de BOM/CRLF) faz toda decisão parecer de outro mockup. `--force` não destrava em nenhum dos dois. Mockup legado (`## S\d+:` e nenhuma `## Tela:`, o template anterior) é isento nas duas fases com `exempt` = `LEGACY_MOCKUP_EXEMPTION` à vista no `getRiskSignals`; qualquer outro heading é "nenhuma tela declarada", senão trocar o heading pularia o checklist (fixture: `test/fixtures/mockups/legacy-ui-mockups.md`, o template de main). Os rótulos dos itens casam com DOIS textos que o parser não vê, os dois com canário em `test/lib/specs/screen-checklist.test.js`: o template do prompt da `morph-uiux` §3 e `framework/templates/docs/ui-mockups.md`; mais o formato de linha que o prompt do avaliador (`morph-review` §4) e a rubrica `contracts.md` ("Contrato de tela") prescrevem. A `Superfície` é validada contra as chaves de `surfaces` da `ui-designer` (`knownSurfaces`) — é a chave que o `morph-plan` §5a usa para somar standards |
149
150
  | `src/lib/tasks/test-scope.js` (o recorte por task, #87) | Este DECIDE, `verify-runner.js` executa: `applyTaskScope` roda UMA vez em `runVerify`, antes do build, e `preDecideRun` (pula `out-of-scope`) e `judgeRun` → `judgeNarrowedRun` (nunca `compareTestCount`; zero ou desconhecido é vermelho) são a cópia única para host e `--linux` — o container recebe o recorte pelo `node.command`. Importa `effectiveGroups` (`test-nodes.js`), `extractCsprojTargets`/`isInside`/`canonicalPath` (`test-runner.js`), `normalizePath` (`traceability-check.js` — um normalizador só para `outputs`), `samePathCase` (`docker-audit.js` — só a CAIXA vem do disco: um path que atravessa symlink/junction fica como escrito, senão o arquivo muda de nó) e a introspecção .NET abaixo. `runs[].selection` sai só no `--json`: `nodeStatuses()` não o persiste. Piso `--minimum-expected-tests` de projeto MTP (#114): quando `floorOverride` o lê de UM lugar, o link recortado ganha `"-p:TestingPlatformCommandLineArguments=<valor com o piso em 1>"` (o `-p:` troca o valor inteiro — medido, sem recompilar); qualquer outra forma roda inteiro, e também o comando que já declara a propriedade (o `-p:` do recorte vem por último e ganharia) e o link que roda mais de um projeto (o `-p:` é global à invocação e trocaria os argumentos do outro). Forma de comando nova se lê e se MEDE (`test/fixtures/test-output/README.md`) antes de estreitar; o resto roda INTEIRO com motivo. Canários: `test/lib/tasks/test-scope.test.js`, o bloco `runVerify — task-scope recorte` de `test/lib/verify-runner.test.js`, `test/commands/verify-filter.test.js` e a prosa em `test/framework/verify-stack-docs-prose.test.js` |
150
- | `src/lib/tasks/dotnet-test-introspection.js` | Só LÊ (dividido de `test-scope.js`): o varredor C# que apaga literais (`blankCSharpLiterals`), o tripwire contra o texto CRU + o balanço de chaves (`csharpClasses`), `classNamesOf`, `dotnetTestMode` (lê o `global.json` por `jsoncToJson` de `src/lib/ai/comment-stripper.js`), o piso `--minimum-expected-tests` / `<VSTestTestCaseFilter>` do projeto, `floorOverride` (#114: o valor do `-p:` que rebaixa o piso a 1 e repassa o resto — só com UM `<TestingPlatformCommandLineArguments>` literal no csproj, sem atributo, num `<PropertyGroup>` sem atributo direto sob `<Project>` (`enclosingElements` anda nas tags: `Condition` no GRUPO, `<Target>` e `<Choose>` injetavam um filtro que o MSBuild não usaria), sem `TreatAsLocalProperty`, sem `<Import>`, sem a propriedade em nenhum outro arquivo que liga o projeto, sem piso no testconfig e sem `` $ @ % ; , & " ' ` \ ! `` no valor; `null` roda inteiro; `bindingMsbuildFiles` é a lista única de arquivos MSBuild que `msbuildDeclares` também lê — o `.csproj.user` e `Directory.Build.props/.targets/.rsp` + `Directory.Packages.props` até a raiz do DISCO, porque o MSBuild não para no repositório), `ignoresExitCode` (o MESMO scan de csproj/Directory.Build.*/testconfig: `--ignore-exit-code` ou `TESTINGPLATFORM_EXITCODE_IGNORE` desarma a prova do recorte MTP que resta quando o sumário é ilegível, o exit code — desde a #115 a contagem do sumário, pt-BR/en-US, prova primeiro, e o scan fica porque decide ANTES da rodada), o projeto de teste dono de um `.cs` (`dotnetTestProjectOf` — só `.cs`/`*.csproj`, via `nearestCsprojOf`), `nearestProjectOf` + `isDotnetTestProject` (qualquer arquivo; cobrem `.csproj`/`.fsproj`/`.vbproj` — `test-scope.js` roda o nó INTEIRO por QUALQUER arquivo não-`.cs` que o nó pode rodar cujo projeto mais próximo é de teste: `.feature`, `.verified.txt`, `.razor`, dado de teste), `isNonCSharpSource`/`isDotnetSource`, `readsLikeTestSource` (o marcador FROUXO no texto cru atrás de `unrecognisedTestFile` e do segundo braço do F2 — falso positivo só custa relógio) e `CS_TEST_ATTRIBUTE` (nome inteiro numa seção de atributo que FECHA — `, TestContext` num construtor e uma coleção `[\n Env.Test,\n]` compravam um verde; o resíduo sondado mora no JSDoc de `csharpClasses`). **Falha para o lado SEGURO:** varredura desbalanceada, literal sem fim, buraco de interpolação cortado ou palavra-chave de tipo que não virou declaração → o nó roda INTEIRO; um erro que ESTREITA roda menos testes e sai verde. `test-scope.js` reexporta `dotnetTestMode`/`classNamesOf`, e `test/lib/tasks/dotnet-test-introspection.test.js` pina que são as MESMAS funções. A garantia e o que ela NÃO cobre moram no JSDoc de `csharpClasses`: mexeu no varredor, reescreva-o junto e prove por mutação que o tripwire ainda morde |
151
- | `src/lib/tasks/linux-runner.js` | `verify-runner.js` (`runNodesInLinux` faz as MESMAS pré-decisões do laço local e julga o resultado pelo MESMO `judgeRun` — uma segunda cópia dessa regra é como container e host passam a discordar da mesma suíte), `framework/hooks/shared/e2e-check.js` (`defaultExec`/`isDockerAvailable` são reusados, nunca recriados), `src/lib/worktree/dirs.js` (nomes do contêiner de worktree nas exclusões do `tar`), `framework/schemas/config.schema.json` (`linuxImage`) e `test/lib/tasks/linux-runner.test.js` (o canário do bind mount: montar a árvore em `/work` é v9fs case-insensitive e não reproduz F35) |
151
+ | `src/lib/tasks/dotnet-test-introspection.js` | Só LÊ (dividido de `test-scope.js`): o varredor C# que apaga literais (`blankCSharpLiterals`), o tripwire contra o texto CRU + o balanço de chaves (`csharpClasses`), `classNamesOf`, `dotnetTestMode` (lê o `global.json` por `jsoncToJson` de `framework/hooks/shared/comment-stripper.js`), o piso `--minimum-expected-tests` / `<VSTestTestCaseFilter>` do projeto, `floorOverride` (#114: o valor do `-p:` que rebaixa o piso a 1 e repassa o resto — só com UM `<TestingPlatformCommandLineArguments>` literal no csproj, sem atributo, num `<PropertyGroup>` sem atributo direto sob `<Project>` (`enclosingElements` anda nas tags: `Condition` no GRUPO, `<Target>` e `<Choose>` injetavam um filtro que o MSBuild não usaria), sem `TreatAsLocalProperty`, sem `<Import>`, sem a propriedade em nenhum outro arquivo que liga o projeto, sem piso no testconfig e sem `` $ @ % ; , & " ' ` \ ! `` no valor; `null` roda inteiro; `bindingMsbuildFiles` é a lista única de arquivos MSBuild que `msbuildDeclares` também lê — o `.csproj.user` e `Directory.Build.props/.targets/.rsp` + `Directory.Packages.props` até a raiz do DISCO, porque o MSBuild não para no repositório), `ignoresExitCode` (o MESMO scan de csproj/Directory.Build.*/testconfig: `--ignore-exit-code` ou `TESTINGPLATFORM_EXITCODE_IGNORE` desarma a prova do recorte MTP que resta quando o sumário é ilegível, o exit code — desde a #115 a contagem do sumário, pt-BR/en-US, prova primeiro, e o scan fica porque decide ANTES da rodada), o projeto de teste dono de um `.cs` (`dotnetTestProjectOf` — só `.cs`/`*.csproj`, via `nearestCsprojOf`), `nearestProjectOf` + `isDotnetTestProject` (qualquer arquivo; cobrem `.csproj`/`.fsproj`/`.vbproj` — `test-scope.js` roda o nó INTEIRO por QUALQUER arquivo não-`.cs` que o nó pode rodar cujo projeto mais próximo é de teste: `.feature`, `.verified.txt`, `.razor`, dado de teste), `isNonCSharpSource`/`isDotnetSource`, `readsLikeTestSource` (o marcador FROUXO no texto cru atrás de `unrecognisedTestFile` e do segundo braço do F2 — falso positivo só custa relógio) e `CS_TEST_ATTRIBUTE` (nome inteiro numa seção de atributo que FECHA — `, TestContext` num construtor e uma coleção `[\n Env.Test,\n]` compravam um verde; o resíduo sondado mora no JSDoc de `csharpClasses`). **Falha para o lado SEGURO:** varredura desbalanceada, literal sem fim, buraco de interpolação cortado ou palavra-chave de tipo que não virou declaração → o nó roda INTEIRO; um erro que ESTREITA roda menos testes e sai verde. `test-scope.js` reexporta `dotnetTestMode`/`classNamesOf`, e `test/lib/tasks/dotnet-test-introspection.test.js` pina que são as MESMAS funções. A garantia e o que ela NÃO cobre moram no JSDoc de `csharpClasses`: mexeu no varredor, reescreva-o junto e prove por mutação que o tripwire ainda morde |
152
+ | `src/lib/tasks/linux-runner.js` | `verify-runner.js` (`runNodesInLinux` faz as MESMAS pré-decisões do laço local e julga o resultado pelo MESMO `judgeRun` — uma segunda cópia dessa regra é como container e host passam a discordar da mesma suíte), `framework/hooks/shared/e2e-check.js` (`defaultExec`/`isDockerAvailable` são reusados, nunca recriados), `src/lib/worktree/dirs.js` (nomes do contêiner de worktree nas exclusões do `tar`), `framework/schemas/config.schema.json` (`linuxImage`) e `test/lib/tasks/linux-runner.test.js` (o canário do bind mount: montar a árvore em `/work` é v9fs case-insensitive e não reproduz F35). Os labels `morph-spec.verify`/`morph-spec.root` do `docker run` e `reapLabeledContainers` (`docker rm -f` por label, nunca por nome) são o que limpa o container efêmero sem tocar nos de outros projetos: `test/lib/tasks/linux-runner.test.js` pina o argv de `run` (filtra `args[0]==='run'`, porque o `ps`/`rm` do reap passam pelo mesmo `exec`). |
153
+ | `src/lib/mutation/**` (`morph-spec mutate`, a prova de mutação do Gate 3 em lote) | `src/commands/validation/mutate.js` (fina), `bin/morph-spec.js`, o bullet `mutate` de `framework/docs/CLI.md`, o bloco `MUTAÇÃO` do prompt do avaliador em `morph-review` §4 e `framework/evals/proof-rubrics/mutation-proof.md`, com os canários `test/framework/morph-review-skill.test.js` (o prompt nunca prescreve `os.utime` e dita o escopo do K), `test/lib/mutation/*` e `test/commands/mutate.test.js`. O mundo falso `test/helpers/mutation-world.js` reproduz o build INCREMENTAL do MSBuild (pula quando nenhuma fonte é mais nova que a DLL): sem essa propriedade o teste da armadilha do mtime não morde. Invariantes: **"sobreviveu" só sai do mutante compilado de verdade contra a suíte INTEIRA** — o subconjunto só decide morte, e quem o escolhe é a ferramenta; restaurar é **escrita nova**, nunca `os.utime` (mtime antigo deixa o binário do último mutante "em dia": medido, build de 1,9 s, nenhum `CoreCompile`); `killed` exige falha NOVA contra a rodada-base do MESMO escopo, e `inconclusive`/`build-error` nunca contam como morte; `proveBinary` compara a DLL final com a construída da árvore limpa. `dotnetBuildCommandFor` (exportado de `test-runner.js`) é a única grafia de "dotnet build" e `csharpClasses` a única leitura de classe de teste; `narrowNode` NÃO serve ao subconjunto porque recusa comando que já declara `--filter` (o `Category!=Eval` do GHLBrain), então o filtro é composto por `withFilter`. Dois parsers de TRX, ou um segundo "dotnet build" digitado, divergem |
152
154
  | `--record-baseline` (`recordTestBaselines` em `src/lib/tasks/test-nodes.js`) | `verify-runner.js` (`baselineTargetProblem` recusa ANTES do build; `recordBaselineFrom` só grava com todos os `runs[]` verdes), `src/commands/validation/verify.js` (não persiste stamp: é manutenção de config, não prova de gate), `framework/schemas/config.schema.json` (`expectedCount`) e `atomicWriteJson` de `state-manager.js` — o número é MEDIDO por comando, nunca digitado (D8). Com `{task}` é erro de uso ANTES do build (`baselineTargetProblem`, #87), e `recordTestBaselines` recusa de novo qualquer medição com `selection` — a segunda barreira mora no gravador |
153
155
  | `src/lib/tasks/eval-node.js` | os leitores do MESMO nó `runner: "evals"`: `test-nodes.js` (o enum de `TEST_RUNNERS` e a validação fail-closed do bloco `evals`), `verify-runner.js` (`preDecideRun` faz o pré-voo — os três estados de cache reprovam ANTES de `dotnet test`; `judgeEvalRun` lê o sumário depois), `verify.js` (`evalsStatusOf` deriva `nodes.evals` do stamp) e `framework/schemas/config.schema.json`. O pin tem UM leitor (`readAiPin`), chamado com duas raízes (`framework/` e `.morph/framework/`) — um segundo parser de `ai-pin.json` é como as duas verdades começam. **`null` é DESCONHECIDO, nunca 0**: sumário ilegível não pode fabricar queda contra `expectedCount`, e miss de cache é `fail` nomeado, JAMAIS `skip`. A unidade do nó é o CENÁRIO: só `judgeEvalRun` compara com `expectedCount` — `judgeRun` passa baseline `null` para `runner: "evals"` (#110 parte 2: os `[Fact]` do extrator contra a baseline de cenários reprovavam um 24/24), e sem sumário legível a contagem é `null`, nunca a do extrator — nem compara nem vira baseline. O sumário (`morph-eval-{id}-{pid}.json` no temp) é APAGADO antes e depois da rodada: um arquivo herdado sob o mesmo pid era lido como desta. `--linux` com nó de eval é erro de uso: o canal `MORPH_EVAL_*` carrega caminhos absolutos do host e o container roda uma cópia em `/work` |
154
156
  | `nodeStatuses()` em `src/commands/validation/verify.js` | os TRÊS blocos `nodes` de `framework/schemas/feature.schema.json`, todos `additionalProperties: false` — `verification.nodes`, `verifications.feature.nodes` e `taskVerifications.*.nodes`. A função é escritora ÚNICA dos três: um campo novo aqui sem os três no schema faz o histórico violar o próprio contrato. Canário: "os DOIS escritores do mapa de nós concordam" em `test/commands/verify.test.js` |
@@ -157,9 +159,17 @@ When modifying framework components, verify the full scope of related files.
157
159
  | `framework/hooks/shared/graph-refresh.js` (o refresh em background) | os dois disparos (`graph-refresh-kick`, depois de Bash/PowerShell com `git`/`gh`/`morph-spec`, e o SessionStart de `worktree-context.js`), `graph build\|refresh` (o MESMO lock por repositório) e `worktree.js` (`seedGraphFromRoot`: a worktree nasce com o grafo da raiz e o registro dela — em dia no HEAD só se a raiz estava). **Só a raiz**: o worker destacado nasce com `cwd` no git common dir e nunca recebe o caminho de uma worktree — no win32 um diretório em uso não se apaga, e o `finish` de uma worktree com refresh correndo falhava no meio. A worktree anda por `morph-spec graph refresh`, em primeiro plano. `runGraphify` roda o graphify com `cwd` no git common dir, nunca na árvore (ele lê o alvo, o ignore e o git pelo argumento — medido no 0.9.50). O disparo lê o HEAD pelo disco (`readPrimaryHead`, `git-paths.js`) e não solta processo quando o grafo não pede refresh: ele roda a cada comando git do agente. O step 18b de `setup-infra.js` não instala o hook git do graphify: ele não roda em worktree, não tem `post-merge` e carimba como em dia um grafo sem os arquivos do merge |
158
160
  | `framework/hooks/shared/git-exclude.js` | ÚNICO escritor dos blocos do morph-spec no `info/exclude` do COMMON dir — o `.git/worktrees/<id>/info/exclude` o git não lê para worktree vinculada. Leitores que passam o seu marcador: `worktree.js`, `handoff-generator.js` e `graph-refresh.js`. Resolve o common dir pelo disco, sem subprocesso: um `git` gerado por um processo destacado abria janela de console no win32 |
159
161
  | `src/lib/validators/reuse/**` | `validation-runner.js` (case + push universal), `framework/rubrics/*.json` (o id `reuse` mapeado numa categoria, senão o achado não pesa no score), `test/fixtures/graph/prospectpro-reuse.graph.json` (recorte do grafo real — regerar com `build-reuse-fixture.mjs`, nunca editar à mão) |
160
- | `src/lib/validators/code-budget/**` | `validation-runner.js` (case + push universal fora do `light`), `framework/rubrics/*.json` (o id `code-budget` em `code_quality` nas TRÊS, senão o aviso não pesa), `framework/docs/CLI.md` (o que conta como teste e os limites 1,5×/40 linhas) e `test/integration/code-budget-verify.test.js`. Lê `addedLinesAgainst` (`default-branch.js`), `isTestFile`/`isProjectSource` (`symbol-scanner.js`, a mesma régua de teste do `reuse`) e `stripComments` com `js` (`comment-stripper.js`). Sempre `warning`. Não conseguir medir é skip `unavailable` com razão, e o `validation-runner` não põe validador pulado em `results`: pulado não conta como verde |
162
+ | `src/lib/validators/code-budget/**` | `validation-runner.js` (case + push universal fora do `light`), `framework/rubrics/*.json` (o id `code-budget` em `code_quality` nas TRÊS, senão o aviso não pesa), `framework/docs/CLI.md` (o que conta como teste e os limites 1,5×/40 linhas) e `test/integration/code-budget-verify.test.js`. Lê `addedLinesAgainst` (`default-branch.js`), `isTestFile` (`symbol-scanner.js`, a mesma régua de teste do `reuse`), `isProjectSource`/`isGenerated` (`hooks/shared/source-files.js`) e `commentLines`/`citesProcess` (`hooks/shared/comment-scanner.js`, sobre `stripComments` com `js`). Sempre `warning`. Não conseguir medir é skip `unavailable` com razão, e o `validation-runner` não põe validador pulado em `results`: pulado não conta como verde |
163
+ | `framework/hooks/shared/comment-rules.js` / `comment-scanner.js` / `source-files.js` (o hook `comment-guard`) | Os dois leitores do MESMO comentário: o hook `comment-guard` (PostToolUse `Write\|Edit`, bloqueia na hora da edição) e o `code-budget` (avisa no `verify`). Moram em `hooks/shared` porque hook não importa `src/`; o `src/` importa daqui, nunca uma cópia. O hook julga só o comentário ADICIONADO (`tool_response.originalFile`, medido: `null` no Write que cria) e só FORMA — processo, banner/`#region`, bloco > 3 linhas, doc-comment fora de pacote publicado —, nunca "repete o código". É o ÚLTIMO elo da chain `Write\|Edit` (um block encerra a chain; canário em `test/hooks/hooks-installer.test.js`). Marcador que template precisa manter (`READS STANDARD(S):`, `VERIFY:`) entra em `ALLOWED`; o canário de template em `test/hooks/comment-rules.test.js` reprova template que dispararia o hook no scaffold |
161
164
  | o ciclo de reuso (`reuse-map.md`) | `output-schema.js` (+ `npm run generate`), `framework/schemas/tasks.schema.json` + `tasks-json-parser.js` (campo `reuse`), `morph-plan` §1c/§5b, template de dispatch de `morph-implement`/`morph-apply`, personas de `agents.json`, `evals/rubrics/code-quality.md` + `condensed.md`, `morph-review` (insumo do avaliador), `test/framework/graph-fallback.test.js` |
162
165
  | `framework/mcp/registry.json` | mcp-installer.js, claude-config-detector.js, agents-installer.js (personas' `mcp` field), `scripts/validate-framework.js` Check 8, `test/framework/mcp-registry.test.js` |
166
+ | `src/lib/detectors/claude-config-detector.js` (`readEnabledPlugins`) e `src/lib/installers/mcp-installer.js` (`getInstalledMcps`) | os QUATRO chamadores de `orchestrateMcpSetup` (`mcp-setup.js` ×3, `init.js`): chamada sem `enabledPlugins` recria a duplicata `mcp__X` × `mcp__plugin_X_X` (canário de fiação em `test/lib/installers/mcp-installer.test.js`). `readEnabledPlugins` é a leitura ÚNICA de "plugin ligado" — o `doctor --procs` a reusa em `procs/mcp-config.js`; o prefixo `plugin_<nome>_` sai de `serverNames` do registry via `mcpServerNames` |
167
+ | `src/lib/procs/**` (`doctor --procs`) | `src/commands/project/doctor-procs.js`, o ramo `--procs` de `doctor.js` (entra DEPOIS do de `--worktrees`, fora das fatias que `test/commands/doctor-ai.test.js` corta), `bin/morph-spec.js` (`--prune` é UMA flag para os dois modos) e `test/commands/doctor-procs.test.js`. **Só `prune.js` mata, e por PID:** o canário varre o grafo estático do comando (`prune.js` só entra por import dinâmico dentro de `if (options.prune)`), reprova escrita, spawn e `process.kill` no caminho de leitura — com exceção nomeada ao `execFileSync` de `list.js` — e reprova `taskkill`/`Stop-Process`/`pkill`/`killall`/`/IM` em qualquer código de `src/` e dos hooks. As proteções de `classify.js` e a checagem de PID reaproveitado de `prune.js` têm teste por mutação sobre processo real: tirar uma delas mata um sleeper de teste. `null` é desconhecido, nunca lista vazia. `MORPH_PROCS_FIXTURE` é o único seam de teste; nada em produção o define |
168
+ | `src/lib/heavy/**` e o núcleo `framework/hooks/shared/heavy-{config,holders,slots,command}.js` (semáforo de nós pesados) + `process-alive.js` | `verify-runner.js` (`runVerify` é só o wrapper que cria UM `createHeavyGate` e o libera no `finally`; `gate.enter` é lazy e roda imediatamente antes do primeiro spawn real nos QUATRO pontos — build, testes, `--linux` e o e2e por `beforeHeavy` em `framework/hooks/shared/e2e-check.js`. Nó novo que spawna processo pesado sem `enter` fura o semáforo: `test/lib/verify-runner-heavy.test.js`), `verify.js`/`e2e.js` (`HeavyBusyError` → exit 3, **sem stamp e sem evento `Verify`**: fila não é gate; `nodeStatuses`/`PARTIAL_RUN_FLAGS` não ganham `busy`), `heavy.js` (`heavy status|run`), `cleanup.js` (`afterHeavyRun` recusa com `!enabled` — sem semáforo nada prova que outra sessão não compila —, fora de `feature|e2e-down|prune`, com holder vivo e com `dotnet build|test` vivo, e roda o `build-server shutdown` sob `runExclusive`), `doctor-procs.js` (holders são protegidos da poda; `afterHeavyRun` no `--prune`), `framework/schemas/config.schema.json` (bloco `heavy`; canário schema × `HEAVY_DEFAULTS` em `test/meta/heavy-isolation.test.js`) e `scripts/run-tests.mjs` (`HEAVY_OFF`: sem `MORPH_HEAVY=off` no env de todo filho a suíte enfileira nos slots REAIS de quem a roda). **O núcleo mora em `hooks/shared`** (hook não importa `src/`; `src/lib/heavy/` guarda só o `gate`, o `cleanup` e o comando): `heavy-holders.js` só lê (é o que o `doctor --procs` alcança no caminho somente leitura) e `heavy-slots.js` escreve; `graph-refresh.js` importa `pidAlive` em vez de ter cópia. **`heavy.gateBash` (opt-in):** `heavy-gate` (`PreToolUse` Bash, depois de todos os guards e antes do `worktree-env-inject`: um comando que um guard bloqueia não segura vaga) e `heavy-release` (`PostToolUse` e `PostToolUseFailure`, esta continua `async`) tomam e devolvem a vaga por `tool_use_id`. **A vaga de lease não tem pid** (`pid: null`, `leaseId`, `ttlMs`): `readLockFile` a vence por TTL, nunca por `pidAlive` — o processo do hook já saiu —, e uma chamada recusada por regra ou interrompida, que não dispara `Post*`, segura a vaga até o TTL. A espera do gate é `min(waitTimeoutSec, 540)`: um `PreToolUse` que estoura o timeout de 600 s deixa a chamada rodar, então a recusa tem de sair dali. **Nunca reescreva o comando para pôr slot:** o `updatedInput` é julgado contra as regras do usuário na forma reescrita. `heavyKind` (`heavy-command.js`) decide pelo executável de cada segmento fora de aspas; forma nova entra na tabela de `test/hooks/heavy-command.test.js` ao lado de um caso leve parecido. Os imports do semáforo em `heavy-gate`/`heavy-release` são **lazy** de propósito (os elos rodam em todo Bash e quase nenhum é pesado). Canários: `test/hooks/heavy-gate.test.js` (medido por mutação, 17 de 17), `heavy-slots`/`heavy-holders`/`heavy-command`, e os pins de ordem e `async` em `test/hooks/hooks-installer.test.js`. Slot vago = pid morto OU idade > `maxHoldMinutes`; `null`/diretório não criável é fail-open (não fabrica fila). `tearDownStack` (`e2e-check.js`) usa `--remove-orphans`: `test/lib/e2e/stack-teardown.test.js` pina o argv |
169
+ | `src/lib/worktree/agent-reaper.js` (reaper de sub-agent) + `framework/hooks/shared/agent-reap.js` | `worktree.js` (`runWorktreeRemove` é o núcleo sem `process.exit` que o reaper chama e `worktreeRemoveCommand` só imprime e sai; `teardownWorktree({ relinkOnFailure })` é opt-in do reaper — o `worktree remove` manual segue deixando a infra desmontada em falha, de propósito), `worktree-audit.js` (`agent-worktree-leftover`; a árvore `agent-<hex>` sai do ramo `morph-task` antigo para não duplicar o `unintegrated-work`), `doctor.js` (`--prune` roda o reaper), `worktree-context.js` (`kickAgentReap` no SessionStart: worker destacado, decisão só pelo disco, throttle de 10 min) e `bin/morph-spec.js`. **Provas — todas, senão preserva, e `null` preserva:** o último evento do `events.jsonl` da árvore é o `AgentUsage` do próprio `agentId`; graça; árvore limpa (`--untracked-files=all` filtrado por `withoutLeftoverIgnore`); zero commit exclusivo por `rev-list --stdin` **sem `--all`** e com os `morph-task/agent-*` fora do conjunto de refs; não é âncora de sessão. **O lock por repositório é obrigatório:** dois reapers concorrentes deixam um relink recriar junction no meio da remoção do outro, e o `--force` as atravessa. Canários: `test/lib/worktree/agent-reaper.test.js` (cada prova sozinha vira o veredito; medido por mutação), `test/commands/doctor-agent-reap.test.js`, `test/hooks/agent-reap.test.js`, `test/framework/worktree-reap-prose.test.js`. O `events.jsonl` da árvore removida vai para `finish-backup/task/` (`.morph/` é shieldable no `ignored-triage`) e some do `cost --all-trees` |
170
+ | `src/lib/worktree/vscode-settings.js` (`.vscode/settings.json`) | os SEIS pontos que o chamam: `init`/`update` (`update.js` pelo `fx`, e `setup-infra.js` step 18a — `init` delega ao `update`, a 2ª chamada sai `unchanged`), `runWorktreeSetup`/`runWorktreeProvision` (`rootAndEditorReport`, fail-open) e `doctor --worktrees --prune`. `planEditorSettings` só lê e `applyEditorSettings` escreve por `fx.writeFile`: o `update --dry-run` só é honesto porque o passo emite nos dois modos (canário de igualdade dry × real em `test/commands/update-dry-run.test.js`). **Só adiciona chave que falta**, nunca altera valor do usuário; comentário, vírgula sobrando, JSON ilegível, raiz não-objeto ou arquivo **rastreado** → não escreve e devolve o `snippet`. O arquivo **criado** vai para o `info/exclude` do common dir (`git-exclude.js`) — sem isso ele é sujeira da raiz e o `finish --merge` recusa. `bin/obj` em `files.exclude` só com stack .NET. `git.detectWorktrees` **não** é escrito de propósito (recurso de listagem, sobrepõe preferência global). `worktree.editorSettings` do `config.schema.json` e `MORPH_EDITOR_SETTINGS=0` desligam. Canários: `test/lib/worktree/vscode-settings.test.js` (medido por mutação, 11 de 11), `test/commands/editor-settings-wiring.test.js` |
171
+ | `framework/hooks/shared/worktree-root.js` (`worktree.root`) | o resolvedor ÚNICO de "onde as worktrees moram" e de "está no contêiner" — a UNIÃO do layout configurado com `worktrees/` e `.worktrees/`, para a árvore antiga seguir reconhecida. Todo leitor precisa dele, e um que volte ao literal fica cego para a externa ou acusa `out-of-container` em árvore boa: `worktree.js` (`buildWorktreePath`, mkdir do pai, `findWorktreeForFeature`), `worktree-audit.js` (`inContainer` e `dirsOnDisk`, que só varre `<root>/<repo>-<hash>/`), `agent-reaper.js`, `worktree-context.js` (`isOutsideContainer`), `worktree-create.js` (fallback em git puro), `registry.js` (`insideWorktreeContainer` lê o `.git` da árvore) e os scanners do dashboard (`features-scanner`, `project-api`). `src/lib/worktree/dirs.js` só reexporta as constantes daqui. **Root dentro da árvore é recusado com aviso:** as listas orientadas pelo nome `worktrees/` (`.gitignore`, `graphifyignore`, `test-runner`, `linux-runner`, `architecture-scanner`, `ai-project-scan`, globs do editor) não o enxergariam. A chave do repositório é `basename-<hash da raiz>`: só o basename colidiria entre repositórios. `buildWorktreePath` preserva a string como veio quando não há root (o teste dele passa `/project`). Canários: `test/hooks/worktree-root.test.js`, `test/commands/worktree-root.test.js` (medido por mutação, 14 de 14) |
172
+ | `src/lib/worktree/worktree-include.js` (`.worktreeinclude`) e o `locked` de `parseWorktreeList` | O hook `WorktreeCreate` **substitui** a criação nativa, e a doc do Claude Code diz que com hook o `.worktreeinclude` "não é processado": `copyWorktreeIncludes` é a paridade, chamado por `runWorktreeSetup` e `runWorktreeProvision` (que ainda copia `.env*` por `copyRootEnvFiles`; a união é de propósito, e o include não repete o que o `.env*` já trouxe). Casa o padrão **e** é ignorado NA ÁRVORE (`check-ignore` lá: arquivo que casa mas não é ignorado sujaria o worktree e o `finish` recusa), nunca `node_modules`/artefato de build (`isDisposableBuildArtifact`, de `ignored-triage.js`)/`.git` aninhado, nunca sobrescreve. Uma segunda noção de "o que copiar" faria o nativo e o morph discordarem. `parseWorktreeList` expõe `locked` (a linha `locked` do porcelain) e o `agent-reaper.js` o lê: `git worktree lock` é a marca que o Claude Code usa em agente rodando, e a árvore travada nunca é tocada (razão `locked`). O SessionStart segue o `cwd` do payload (`sessionDir` em `worktree-context.js`) só para worktree vinculado do MESMO repositório — `CLAUDE_PROJECT_DIR` fica na raiz numa sessão que entra no worktree —, e usa o `isSecondaryWorktree` de `git-paths.js` (a cópia local comparava `--git-dir` com `--git-common-dir` em formas diferentes e tratava uma SUBPASTA da raiz como worktree). Canários: `test/lib/worktree/worktree-include.test.js`, `test/hooks/worktree-context.test.js`, `test/lib/worktree/agent-reaper.test.js` |
163
173
  | Any `framework/` file that calls `morph-spec graph` | The fallback to grep must be declared in the SAME file — `test/framework/graph-fallback.test.js` enforces it (ADR-002: a skill that requires the graph with no fallback is a bug) |
164
174
  | `framework/hooks/claude-code/core/worktree-create.js` / `worktree-remove.js` / `block-raw-worktree-add.js` / `worktree-env-inject.js` | `hooks-installer.js` (`MORPH_HOOKS`: eventos `WorktreeCreate`/`WorktreeRemove` + a ORDEM da chain Bash — inject por último, um comando bloqueado nunca é reescrito), `hooks-scanner.js` `EVENT_KEYS`, `chain.js` (`updatedInput` só viaja com rewrite — `permissionDecision:'allow'` incondicional pularia o prompt de todo Bash), `worktree.js` (`runWorktreeProvision`, `--if-clean`, `envKeyForWorktree`), `worktree-audit.js` (`morph-task/*`, `out-of-container`), `worktree-context.js`, MORPH.md §2.2, `morph-apply` §3b-w, `test/framework/worktree-governance-prose.test.js`, `resident-size.test.js` (MORPH.md está NO teto). **Âncora de sessão:** `anchorSessionCommand` (`TEARDOWN_RE`) exporta `MORPH_SESSION_PROJECT_DIR`, que `sessionAnchoredInWorktree` (`worktree.js`) lê e que o `finish` (`--merge`/`--pr` adiam o desmonte) e o `worktree remove` (recusa `session-anchored-in-worktree`) consomem — mudar a chave ou o formato do comando num lado só devolve o bug de 48 hooks `MODULE_NOT_FOUND` depois do `finish` |
165
175
  | `src/lib/worktree/env-allocator.js` (`portLayout`) | os DOIS escritores do bloco — `buildEnvFileContent` (`.morph/worktree.env`) e `compose-env.js` (`buildComposeEnv`, usado por `e2e up`, pelo nó `e2e` do `verify`, por `stack-teardown.js` e por `morph-spec env`) — e os leitores `worktree-context.js`, `handoff-generator.js`, `worktree-env-inject.js`, o template `docker-compose.template.yml` (variáveis que ele publica) e o standard `local-compose-isolation.md` (tabela de offsets). Canário: "worktree.env e compose env concordam byte a byte" em `test/lib/e2e/compose-env.test.js`. Offset novo aqui sem entrar no template é porta que ninguém publica |
@@ -168,7 +178,7 @@ When modifying framework components, verify the full scope of related files.
168
178
  | `src/lib/risk-detector.js` | os DOIS lados da mesma decisão de gate: `gate-check.js` (`collectSignals` COLETA do disco; nenhum sinal que descreva um FATO pode entrar em `OVERRIDABLE_SIGNAL_KEYS` — `gate`/`workType`/`e2e*` são o precedente), `src/commands/validation/verify.js` (o stamp `verification` é a ÚNICA coisa que o gate lê: campo novo aqui só vira sinal se o stamp o gravar), `framework/schemas/feature.schema.json` (`verification` é `additionalProperties: false`), `framework/MORPH.md` §5 (tabela de sinais — ela se declara fonte única) e `framework/docs/CLI.md`. Política escrita no coletor cria duas fontes para o mesmo fato — foi assim que `gate-check` e `approve` divergiram uma vez sobre o score do Gate 3. **`verificationScope` é sinal, não auditoria**: a dispensa de e2e (`project`/`scope`) só conta vinda de um stamp de escopo `feature` — um `verify {f} {task}` de task `group: docs` carimba a razão de dispensa e compraria o Gate 3 da feature inteira. **E o escopo de task pausa o ship por si só** (#87): no `review`, `verificationScope` string ≠ `'feature'` pausa ANTES do bloco de e2e, mesmo com todos os nós verdes — o recorte automático faz TODO `verify {f} {task}` rodar menos que a suíte; `null` (stamp legado) não pausa por esta regra. O par mora em `verify.js`: `--filter` de escopo de feature não persiste o stamp (`reason: 'filtered'`), nem `--skip-tests`/`--skip-build` (`'skip-tests'`/`'skip-build'`, #113 — nó pulado não reprova o rollup e nenhuma regra do gate lê `tests`/`build` pulados; a lista é `PARTIAL_RUN_FLAGS`) |
169
179
  | `src/lib/installers/effects.js` | os NOVE pontos de emissão que atravessam o caminho de `update`: `update.js` (clean, payloads, spawn de `generate-refs.js`, `gitIndex`), `skills-installer.js`, `agents-installer.js`, `setup-infra.js` (`installRulesForStack`, `updateClaudeMdAgentsBlock`), `hooks-installer.js` (`installClaudeHooks`, `installGlobalStatusline`), `file-copier.js` (`updateGitignore`, `ensureGitattributes`), `claude-md-injector.js`, `version-checker.js` (`saveProjectMorphVersion`) e `templates-reconcile.js`. **O caminho real também emite** — o resumo de uma execução real sai de `list()`, e é por isso que um ponto de emissão esquecido some das DUAS saídas ao mesmo tempo. Canários: `test/lib/installers/effects.test.js` (igualdade dry × real do MESMO roteiro) e `test/commands/update-dry-run.test.js` — que tem DOIS canários e precisa dos dois: **igualdade** prova que os dois lados percorrem o mesmo caminho, **diferencial** (mudar `--templates` muda a previsão) prova que o caminho lê a flag. Só igualdade aprova uma previsão e uma execução que ignoram a MESMA flag — foi medido nesta feature, por mutação. Um laço de limpeza de órfão novo tem de nascer calado em `fx.dryRun` (D6): o clean o subsome. E a lista de `frameworkDirsToClean` é pinada **à mão** no teste (`DIRETORIOS_APAGADOS_POR_INTEIRO`, dez caminhos, `deepEqual` ordenado): derivar a expectativa chamando a própria função deixou a suíte inteira cega — medido, dava para remover 2 das 10 entradas sem matar nenhum dos 207 arquivos de teste |
170
180
  | `src/lib/ai/**` (`doctor --ai`) | `src/commands/project/doctor.js` (`doctorAiCommand` + a guarda de uso indevido), `bin/morph-spec.js` (`--ai`), `framework/docs/CLI.md`. **`--ai` NUNCA entra no `doctor --full` nem no default** — o renderer do `--full` manda tudo que não é `ok`/`warn` para `✗` e `test/commands/doctor.test.js` exige zero `✗` neste repositório. **Nada alcançável por `--ai` pode escrever**: `test/commands/doctor-ai.test.js` varre o grafo de imports E o corpo de `doctorAiCommand` (com os imports dinâmicos fixados nos dois permitidos), reprovando `writeFileSync`/`spawn`/`fs-extra` em CÓDIGO — comentários apagados antes, porque os módulos EXPLICAM por escrito que não fazem isso e medir a menção seria medir uma cópia do objeto. `null` é DESCONHECIDO e vira `skip` **com razão obrigatória**; `[]` é "perguntei e não achei" e vira `ok` |
171
- | `src/lib/ai/registry-audit.js` | o leitor de PRODUÇÃO do mesmo documento: `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs` (`CommentHandling = Skip`, `AllowTrailingCommas`, `AllowedAliasProperties` com `pricing`/`pricingWaiver`) e o exemplar `model-registry.v2.json` que o framework manda COPIAR. A auditoria tem de enxergar a **mesma forma** que o leitor de produção enxerga: com `JSON.parse` estrito, duas das cinco checagens saíam `skip` sobre o exemplar canônico — e `skip` não muda exit code, então o silêncio era total. A tolerância a JSONC vive em `src/lib/ai/comment-stripper.js`, ÚNICA (o `code-audit.js` usa o mesmo varredor, e o `code-budget` também, o único com a opção `js` — template e regex literal de TS/JS; sem ela o varredor é o de antes) — e a paridade é nos DOIS sentidos: `jsoncToJson` **lança** no comentário de bloco não fechado, porque o `JsonCommentHandling.Skip` do .NET lança, e ser mais frouxo que o kit é aprovar documento que a app recusa carregar. Ao mascarar strings, **as aspas delimitadoras ficam**: apagá-las junto faz um elemento-string virar espaço puro e o detector de vírgula sobrando comer um SEPARADOR real (`{"tags":["a","b"]}` saía inválido). Canário obrigatório: array terminado em **string**, com e sem vírgula sobrando — uma suíte só com arrays de número passa dos dois lados dessa inversão. A **REGRA DURA** do exemplar — *todo alias declara `pricing` OU `pricingWaiver`, nunca os dois, nunca nenhum* — é do alias, não de um mapa no topo: ler só o mapa dava 6 falsos positivos sobre um documento correto. O canário **copia o exemplar do disco**, nunca uma paráfrase dele |
181
+ | `src/lib/ai/registry-audit.js` | o leitor de PRODUÇÃO do mesmo documento: `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs` (`CommentHandling = Skip`, `AllowTrailingCommas`, `AllowedAliasProperties` com `pricing`/`pricingWaiver`) e o exemplar `model-registry.v2.json` que o framework manda COPIAR. A auditoria tem de enxergar a **mesma forma** que o leitor de produção enxerga: com `JSON.parse` estrito, duas das cinco checagens saíam `skip` sobre o exemplar canônico — e `skip` não muda exit code, então o silêncio era total. A tolerância a JSONC vive em `framework/hooks/shared/comment-stripper.js`, ÚNICA (o `code-audit.js` usa o mesmo varredor, e o `code-budget` também, o único com a opção `js` — template e regex literal de TS/JS; sem ela o varredor é o de antes) — e a paridade é nos DOIS sentidos: `jsoncToJson` **lança** no comentário de bloco não fechado, porque o `JsonCommentHandling.Skip` do .NET lança, e ser mais frouxo que o kit é aprovar documento que a app recusa carregar. Ao mascarar strings, **as aspas delimitadoras ficam**: apagá-las junto faz um elemento-string virar espaço puro e o detector de vírgula sobrando comer um SEPARADOR real (`{"tags":["a","b"]}` saía inválido). Canário obrigatório: array terminado em **string**, com e sem vírgula sobrando — uma suíte só com arrays de número passa dos dois lados dessa inversão. A **REGRA DURA** do exemplar — *todo alias declara `pricing` OU `pricingWaiver`, nunca os dois, nunca nenhum* — é do alias, não de um mapa no topo: ler só o mapa dava 6 falsos positivos sobre um documento correto. O canário **copia o exemplar do disco**, nunca uma paráfrase dele |
172
182
  | `src/lib/worktree/dirs.js` | ÚNICA fonte do nome do container (`worktrees/`, sem ponto — Jest esconde diretório com ponto e reporta 0 testes sem erro). `worktree.js` re-exporta; `graphifyignore.js`, `file-copier.js`, `architecture-scanner.js`, `test-runner.js`, `worktree-audit.js`, os scanners do dashboard (`features-scanner.js`, `project-api.js`), `tasks/linux-runner.js` (exclusões do `tar` do `--linux`) importam. `test/lib/worktree/dirs.test.js` cobra cada leitor |
173
183
  | `src/dashboard/**` (páginas e scanners) | `test/dashboard/pages-smoke.test.js` renderiza as 7 rotas e os painéis de detalhe pela config Vite do dashboard com os dados REAIS do scanner — build verde não prova nada (8.38.0 saiu com `ReferenceError` na rota inicial). Campo que a página lê tem de sair do scanner: `activePhases` é DERIVADO das linhas de dispatch das skills de fase (`personaPhases`, com `isImplementer`), nunca uma lista à mão. Edge do React Flow só existe no cliente (chave duplicada não aparece no SSR — o dedupe de `standardRefs` mora no scanner). `src/dashboard/.gitignore` é o que mantém `node_modules` fora do tarball (`test/dashboard/packaging.test.js`). `scanner/feature-status.js` (`isFinished`) é a regra ÚNICA de "feature encerrada" do servidor (portfólio, `awaiting`, `taskIndex`) e do cliente (lista e quadro) — arquivada conta como encerrada seja qual for o `status` legado. O plugin engole o `full-reload` do Vite para `.html` sob `.morph/` (`isMorphDataReload`): `handleHotUpdate` devolvendo `[]` NÃO segura `.html` no Vite 6, e um agente gravando um mockup recarregava o dashboard. `resolveFeatureTree` (git, ~0,3 s por chamada) é lembrado por feature no `project-api.js` e só é esquecido por evento estrutural (`add`/`unlink`/`addDir`/`unlinkDir`) ou TTL de 60 s — um scan síncrono trava o event loop do dev server inteiro |
174
184
  | `src/lib/projects/registry.js` (projetos da máquina, `~/.morph-spec/projects.json`) | os TRÊS escritores automáticos — `init`, `update` (nunca sob `--dry-run`) e `dashboard` — mais o add/remove do portfólio (`src/dashboard/scanner/portfolio.js`). Registrar é fail-open: registro ilegível ou não gravável nunca derruba o comando. Nunca registra pasta sob o temp do sistema (as suítes rodam `init`/`update` ali às centenas, inclusive por CLI em spawn que herda o home real) nem checkout de worktree (`WORKTREE_DIR_NAMES`). Teste que registra usa `MORPH_PROJECTS_REGISTRY` apontando para um temp, ou o seam `tempRoot` — nenhuma suíte escreve o `~/.morph-spec` de quem roda. Leitura poda projeto cuja pasta sumiu; descoberta é UM nível abaixo das pastas-mãe e só sugere, nunca adiciona |
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
4
4
 
5
5
  **Package:** `@polymorphism-tech/morph-spec`
6
- **Version:** 8.41.0
6
+ **Version:** 8.43.0
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -245,7 +245,7 @@ Guardrail hooks are installed into `.claude/settings.local.json` across 7 event
245
245
  | `SessionStart` | `worktree-context.js` | Injects feature identity (worktree) or active-feature summary (root) |
246
246
  | `PreToolUse` (Write\|Edit) | `protect-spec-files.js`, `enforce-phase-writes.js` | Blocks edits to the frozen plan contract post-Gate-2; enforces phase folders |
247
247
  | `PreToolUse` (Bash) | `pre-bash-denylist.js` | Detects destructive shell patterns |
248
- | `PostToolUse` | `post-edit-typecheck.js`, `tasks-json-guard.js`, `trace-autogen.js` | Stack-aware typecheck; guards `tasks.json` plan-contract edits; auto-generates execution traces |
248
+ | `PostToolUse` | `post-edit-typecheck.js`, `tasks-json-guard.js`, `trace-autogen.js`, `comment-guard.js` | Stack-aware typecheck; guards `tasks.json` plan-contract edits; auto-generates execution traces; blocks added comments the comment standard forbids |
249
249
  | `PostToolUse` | `state-sync.js`, `gate-guard.js`, `loop-detect.js` | Syncs feature state; enforces gate ordering; detects repeated-action loops |
250
250
  | `Stop` | `validate-completion.js` | Warns about incomplete tasks, missing outputs, or pending approval gates |
251
251
 
@@ -379,4 +379,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
379
379
 
380
380
  ---
381
381
 
382
- *morph-spec v8.41.0 by [Polymorphism Tech](https://polymorphism.tech)*
382
+ *morph-spec v8.43.0 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -30,11 +30,14 @@ const installPluginCommand = lazy('../src/commands/project/install-plugin-cmd.js
30
30
  const updateCommand = lazy('../src/commands/project/update.js', 'updateCommand');
31
31
  const doctorCommand = lazy('../src/commands/project/doctor.js', 'doctorCommand');
32
32
  const statusCommand = lazy('../src/commands/project/status.js', 'statusCommand');
33
+ const heavyStatusCommand = lazy('../src/commands/project/heavy.js', 'heavyStatusCommand');
34
+ const heavyRunCommand = lazy('../src/commands/project/heavy.js', 'heavyRunCommand');
33
35
  const worktreeSetupCommand = lazy('../src/commands/project/worktree.js', 'worktreeSetupCommand');
34
36
  const worktreeProvisionCommand = lazy('../src/commands/project/worktree.js', 'worktreeProvisionCommand');
35
37
  const worktreeLinkCommand = lazy('../src/commands/project/worktree.js', 'worktreeLinkCommand');
36
38
  const worktreeListCommand = lazy('../src/commands/project/worktree.js', 'worktreeListCommand');
37
39
  const worktreeRemoveCommand = lazy('../src/commands/project/worktree.js', 'worktreeRemoveCommand');
40
+ const worktreeReapCommand = lazy('../src/commands/project/worktree-reap.js', 'worktreeReapCommand');
38
41
  const branchPruneCommand = lazy('../src/commands/project/branch.js', 'branchPruneCommand');
39
42
  const fleetCommand = lazy('../src/commands/project/fleet.js', 'fleetCommand');
40
43
  const envCommand = lazy('../src/commands/project/env.js', 'envCommand');
@@ -73,6 +76,7 @@ const graphExplainCommand = lazy('../src/commands/graph/explain.js', 'graphExpla
73
76
  const validateCommand = lazy('./validate.js', 'validateCommand');
74
77
  const validateFeatureCommand = lazy('../src/commands/validation/validate-feature.js', 'validateFeatureCommand');
75
78
  const verifyCommand = lazy('../src/commands/validation/verify.js', 'verifyCommand');
79
+ const mutateCommand = lazy('../src/commands/validation/mutate.js', 'mutateCommand');
76
80
  const e2eUpCommand = lazy('../src/commands/e2e/e2e.js', 'e2eUpCommand');
77
81
  const e2eStatusCommand = lazy('../src/commands/e2e/e2e.js', 'e2eStatusCommand');
78
82
  const e2eDownCommand = lazy('../src/commands/e2e/e2e.js', 'e2eDownCommand');
@@ -154,8 +158,9 @@ program
154
158
  .option('--mcp', 'Check MCP server configuration')
155
159
  .option('--ai', 'Auditar um projeto .NET com agentes: pin × projeto, API renomeada, tarifa e `api` por alias, prompt sem fonte')
156
160
  .option('--worktrees', "Reconcile git's worktree registry × the directories on disk × known features")
157
- .option('--prune', "With --worktrees: drop git records for worktree directories that are already gone, clear any feature's root worktree registration pointing at one of them, and apply the finish-backup retention (.git/morph-spec/finish-backup) — the only path that also prunes backups written before it (no manifest)")
158
- .option('--json', 'With --worktrees: output as JSON')
161
+ .option('--procs', 'Listar node/dotnet/testhost/esbuild/chrome headless sem pai vivo, MCP duplicado (projeto × plugin) e .wslconfig ausente. Somente leitura; os consertos de MCP e WSL são só sugeridos')
162
+ .option('--prune', "With --worktrees: drop git records for worktree directories that are already gone, clear any feature's root worktree registration pointing at one of them, and apply the finish-backup retention (.git/morph-spec/finish-backup) — the only path that also prunes backups written before it (no manifest). With --procs: encerrar por PID só os órfãos sem proteção (nunca por nome de imagem, nunca MCP configurado nem worker de build)")
163
+ .option('--json', 'With --worktrees or --procs: output as JSON')
159
164
  .option('--reset', 'Remove morph-managed entries from .claude/settings.local.json')
160
165
  .action(doctorCommand);
161
166
 
@@ -415,6 +420,19 @@ program
415
420
  verifyCommand(feature, task, { ...options, noPersist: options.persist === false })
416
421
  );
417
422
 
423
+ program
424
+ .command('mutate [feature]')
425
+ .description('Prova de mutação do Gate 3 em lote: lê o manifesto de mutantes, roda todos (um build de schemata em .NET, subconjunto primeiro, suíte inteira só para sobrevivente), restaura os bytes e imprime a seção `## Mutações`')
426
+ .option('--plan <file>', 'Manifesto JSON (default: .morph/features/<feature>/4-review/mutants.json)')
427
+ .option('--out <file>', 'Onde gravar a seção `## Mutações` (default: 4-review/mutation-section.md)')
428
+ .option('--no-subset', 'Sem subconjunto: cada mutante roda a suíte inteira')
429
+ .option('--allow-dirty', 'Permite mutar arquivo com alteração não commitada (o snapshot é o único caminho de volta)')
430
+ .option('--jobs <n>', 'Rodadas de subconjunto em paralelo (default 2); cada worker toma um slot heavy livre, e sem slot livre a rodada degrada para sequencial', (v) => Number.parseInt(v, 10))
431
+ .option('--timeout <ms>', 'Teto de cada comando de build/teste', (v) => Number.parseInt(v, 10))
432
+ .option('--restore', 'Recupera o disco de uma rodada que morreu no meio: restaura os bytes do journal, sem rodar nada')
433
+ .option('--json', 'Output as JSON')
434
+ .action((feature, options) => mutateCommand(feature, options));
435
+
418
436
  // ─── Scaffolding ──────────────────────────────────────────────────────────
419
437
  const scaffoldCmd = program
420
438
  .command('scaffold')
@@ -487,6 +505,23 @@ e2eCommand
487
505
  .option('--json', 'Output as JSON')
488
506
  .action((feature, options) => e2eDownCommand(feature, options));
489
507
 
508
+ // ─── Heavy nodes (build, tests, e2e) ─────────────────────────────────────
509
+ const heavyCommand = program
510
+ .command('heavy')
511
+ .description('Slots por usuário para os nós pesados (build, testes, e2e), compartilhados entre as sessões');
512
+
513
+ heavyCommand
514
+ .command('status')
515
+ .description('Quem segura os slots, a RAM livre e o diretório do semáforo (somente leitura)')
516
+ .option('--json', 'Output as JSON')
517
+ .action((options) => heavyStatusCommand(options));
518
+
519
+ heavyCommand
520
+ .command('run <command...>')
521
+ .description('Rodar um comando pesado dentro de um slot: morph-spec heavy run [--label x] -- dotnet build. Repassa o exit code; exit 3 = ocupado, não é vermelho')
522
+ .option('--label <label>', 'Nome do que roda, mostrado a quem espera pelo slot')
523
+ .action((command, options) => heavyRunCommand(command, options));
524
+
490
525
  // ─── Worktree (optional, isolation) ───────────────────────────────────────
491
526
  const worktreeCommand = program
492
527
  .command('worktree')
@@ -531,6 +566,17 @@ worktreeCommand
531
566
  .option('--json', 'Output as JSON (always JSON)')
532
567
  .action((feature, options) => worktreeRemoveCommand(feature, options));
533
568
 
569
+ // Sub-agent trees (`agent-<hex>` on `morph-task/agent-<hex>`) outlive the agent: nothing
570
+ // tells the harness the agent is done with its worktree. The reaper removes only what it can prove
571
+ // is finished and lost-proof; the rest is listed with the reason.
572
+ worktreeCommand
573
+ .command('reap')
574
+ .description('Remove the worktrees and branches of sub-agents that stopped clean (nothing uncommitted, no commit only they hold); everything else is listed with the reason and left alone')
575
+ .option('--dry-run', 'Report what would be removed without touching anything')
576
+ .option('--grace <minutes>', 'Minutes a stopped agent must stay quiet before its tree is reaped (default 30, or worktree.reap.graceMinutes)')
577
+ .option('--json', 'Output as a single JSON line')
578
+ .action((options) => worktreeReapCommand(options));
579
+
534
580
  worktreeCommand
535
581
  .command('list')
536
582
  .description('List active feature worktrees with their phase and gate status')
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "8.41.0",
3
+ "version": "8.43.0",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",
@@ -187,4 +187,4 @@ Never edited directly — go through CLI commands or `morph-spec update` instead
187
187
 
188
188
  ---
189
189
 
190
- *morph-spec v8.41.0 by Polymorphism Tech*
190
+ *morph-spec v8.43.0 by Polymorphism Tech*
@@ -249,4 +249,4 @@ morph-spec doctor
249
249
 
250
250
  ---
251
251
 
252
- *morph-spec v8.41.0 by Polymorphism Tech*
252
+ *morph-spec v8.43.0 by Polymorphism Tech*
@@ -20,7 +20,7 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
20
20
  - Editar `.morph/specs/usecases/` à mão — só o `finish` escreve lá. Mudar um UC existente (`bug`): copie para `2-plan/usecases/` da feature e corrija lá.
21
21
  - Listar perguntas em texto simples — sempre `AskUserQuestion` (1-4 perguntas por chamada).
22
22
  - Usar `Guid.NewGuid()`, classes não-sealed sem justificativa, exceptions como fluxo de controle, camada de Service/Manager entre handler e dados, repository genérico (`IRepository<T>`/`IUnitOfWork`), ou SQL cru sem justificativa.
23
- - Comentar o que nome, tipo ou teste já dizem: o padrão é nenhum comentário (`architecture-code-comments`).
23
+ - Comentar o que nome, tipo ou teste já dizem: o padrão é nenhum comentário, mesmo ao lado de código comentado; o elo com a pós-condição vai no nome do teste (`architecture-code-comments`, hook `comment-guard`).
24
24
  - Editar `taskScores`, `gateDecisions` ou gates de `feature.json` à mão — sempre via `morph-spec score`/`gate-check`/`approve` (timestamps server-side; Edit manual colide com o hook `state-sync`).
25
25
 
26
26
  **SEMPRE:**
@@ -61,7 +61,7 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
61
61
  | `retype <f> <tipo>` | Muda o workType mid-flight; bloqueado após o Gate 3 |
62
62
  | `finish <f> --pr\|--merge` | Encerramento único: arquiva, promove use cases, desmonta worktree. Integra só no `--merge`; `--pr` abre PR e fica `aguardando-merge` até alguém mergear |
63
63
  | `archive <f>` | Arquiva sem integrar (legadas/faxina) |
64
- | `worktree setup\|provision\|link\|remove\|list` | Ciclo do worktree; `provision` = isolamento de task; `remove` é a única remoção segura |
64
+ | `worktree setup\|provision\|link\|remove\|list\|reap` | Ciclo do worktree; `provision` = isolamento de task; `remove` é a única remoção segura |
65
65
  | `branch prune [--dry-run]` | Apaga branch já integrada, inclusive squash-merge; nunca a com PR aberto |
66
66
  | `handoff <f>` | Regenera o documento de retomada da feature |
67
67
  | `fleet` | Visão agregada de toda feature ativa (raiz + worktrees) |
@@ -83,14 +83,15 @@ Prefixo de todo comando: `morph-spec`. Flags/exemplos: `.morph/framework/CLI.md`
83
83
  | Skill | Quando usar |
84
84
  |-------|-------------|
85
85
  | `morph-brainstorming` | Entender negócio, escanear stack, produzir proposal.md |
86
- | `morph-stack-scan` | Detectar desvios de stack em projeto brownfield |
87
- | `morph-uiux` | Ramo opcional uiux — design system, mockups, componentes, fluxos |
86
+ | `morph-stack-scan` | Desvios de stack em projeto brownfield |
87
+ | `morph-uiux` | Ramo opcional de UI/UX — design system e mockups |
88
88
  | `morph-plan` | Destilar use cases e produzir spec.md + mandate.md + tasks.json |
89
89
  | `morph-implement` | Executar uma tarefa com auto-advance entre tasks |
90
90
  | `morph-eval` | Aplicar rubricas e produzir score 0-10 |
91
91
  | `morph-review` | Produzir review-report.md (Gate 3) |
92
- | `morph-hotfix` | ADW cirúrgico de hotfix — scout → fix → gate humano → ship |
92
+ | `morph-hotfix` | Hotfix cirúrgico (`/morph-hotfix`) |
93
93
  | `morph-standards` | Localizar standard sob demanda |
94
+ | `morph-machine` | RAM/CPU: fila pesada, órfãos, worktrees sobrando |
94
95
 
95
96
  ## Roteamento por tipo de trabalho
96
97
 
@@ -118,14 +118,14 @@ Features independentes podem ser desenvolvidas **em paralelo, cada uma no seu gi
118
118
 
119
119
  - **Uma feature = um worktree = uma instância.** Nunca abra duas instâncias na mesma feature (setup recusa, exit 3).
120
120
  - `feature.json` viaja **commitado na branch** `morph/{f}`; a cópia da raiz é esqueleto do nascimento, nunca mais atualizado — leitores (`list`/`handoff`/`fleet`) seguem a branch, mutadores recusam gravar fora da árvore dona (detalhes: CLI.md). `state.json` do worktree é índice derivável — self-heal, nunca sincronizado à mão.
121
- - **Só o morph cria worktree:** `worktree setup <f>` (feature) ou `worktree provision <n>` (isolamento de task, branch `morph-task/<n>`). `git worktree add` cru é bloqueado; `EnterWorktree`/`-w`/`isolation:"worktree"` passam pelo hook `WorktreeCreate` e caem em `worktrees/`. Fora de `worktrees/` = criado sem o morph (`doctor --worktrees` acusa). Infra (`node_modules`, `.claude`, `.morph/framework`) por junction; faltou → `worktree link --all`.
122
- - **Só o morph remove worktree.** Remoção genérica — `git worktree remove` (com OU sem `--force`), `rm -rf`, `Remove-Item -Recurse`, explorador/IDE — **atravessa a junction** e apaga o conteúdo real da raiz (`.claude`/`node_modules`/`.morph/framework`), saindo com sucesso (já esvaziou uma raiz). Só `morph-spec` desmonta a junction ANTES de remover (PROVE-or-DECLINE): **fechar a feature** = `morph-spec finish <f> --pr|--merge`; **só desmontar o worktree** = `morph-spec worktree remove <f>` — ou `--target <path>` para worktree sem feature. O hook barra a versão crua, mas a regra vale antes dele.
123
- - **Ambiente dedicado:** setup escreve `.morph/worktree.env` (`PORT`, `API_PORT`/`ASPNETCORE_URLS`, `POSTGRES_PORT`, `COMPOSE_PROJECT_NAME=morph-{f}`); cada worktree sobe o **seu** compose. O hook `PreToolUse:Bash` injeta as variáveis em dev server/compose; fora do Claude, `eval "$(morph-spec env)"`. Porta fixa ou `container_name` no compose **reprova o nó e2e** em worktree. `finish` derruba o stack **com volumes** (`--keep-volumes` preserva). Neon branch opcional, credencial em `e2e.auth.envFile`, nunca em `worktree.env`. Migration destrutiva → integre antes de abrir irmãos.
121
+ - **Só o morph cria worktree:** `worktree setup <f>` (feature) ou `worktree provision <n>` (isolamento de task, branch `morph-task/<n>`). `git worktree add` cru é bloqueado; `EnterWorktree`/`-w`/`isolation:"worktree"` passam pelo hook `WorktreeCreate` e caem no contêiner (`worktrees/` ou `worktree.root`); fora dele = criado sem o morph (`doctor --worktrees`). Já existe: abra o `claude` DENTRO dela; `EnterWorktree(path)` sempre pede aprovação. Infra (`node_modules`, `.claude`, `.morph/framework`) por junction; faltou → `worktree link --all`.
122
+ - **Só o morph remove worktree.** Remoção genérica — `git worktree remove` (com OU sem `--force`), `rm -rf`, `Remove-Item -Recurse`, explorador/IDE — **atravessa a junction** e apaga o conteúdo real da raiz (`.claude`/`node_modules`/`.morph/framework`), saindo com sucesso. Só `morph-spec` desmonta a junction ANTES de remover (PROVE-or-DECLINE): **fechar a feature** = `morph-spec finish <f> --pr|--merge`; **só desmontar o worktree** = `morph-spec worktree remove <f>` — ou `--target <path>` para worktree sem feature. Sobra de sub-agent (`agent-<hex>`): `worktree reap` remove só a que parou limpa e sem commit exclusivo.
123
+ - **Ambiente dedicado:** setup escreve `.morph/worktree.env` (`PORT`, `API_PORT`/`ASPNETCORE_URLS`, `POSTGRES_PORT`, `COMPOSE_PROJECT_NAME=morph-{f}`); cada worktree sobe o **seu** compose. O hook `PreToolUse:Bash` injeta as variáveis em dev server/compose; fora do Claude, `eval "$(morph-spec env)"`. Porta fixa ou `container_name` no compose **reprova o nó e2e** em worktree. `finish` derruba o stack **com volumes** (`--keep-volumes` preserva). Migration destrutiva → integre antes de abrir irmãos. Build/testes/e2e disputam 2 slots por usuário (`heavy status`): **exit 3 = fila cheia, não vermelho** — repita; fora do `verify`, `heavy run -- <cmd>`.
124
124
  - **Limitação:** `node_modules` é junction compartilhada — dependência mudada afeta os irmãos; integre antes de seguir em paralelo.
125
125
  - **Handoff entre sessões:** `.morph/features/{f}/handoff.md` — retomada comprimida, regenerável (`morph-spec handoff <f>`, resolve a árvore dona sozinho), **aponta** para os artefatos em vez de copiá-los. Regenerado em `setup`/`create --worktree`/`advance`/`approve`. **Leia-o antes de re-derivar contexto à mão** — nunca edite manualmente.
126
- - **Visão da frota:** `fleet` agrega raiz + worktrees (fase, gates, tasks, portas, handoff, commit, sujeira); `worktree list` é a visão enxuta por worktree.
127
- - **Ownership de arquivos:** sem lock runtime — colisão resolve **em plan-time** (`dag` detecta overlap de `outputs`, recomenda `isolation: "worktree"`) e **por construção** (uma feature = um worktree = um escritor). Arquivos compartilhados convergem no merge do `finish`; conflito ali é escopo sobreposto, não bug.
128
- - **Fechamento:** aprove o Gate 3, depois **`morph-spec finish <f> --pr|--merge`** — encerramento único; `--merge` integra na hora, **`--pr` não** — só abre o PR. **Arquiva** commitando `features/{f} → archive/{f}` **dentro de `morph/{f}`**: `--merge` faz `--no-ff` **local** (sem push; recusa se sujo; aborta em conflito); `--pr` faz `push` + `gh pr create` (exige `origin`+`gh`, checados **antes** de arquivar) — fica `aguardando-merge` até o merge, **nunca "entregue" só por `finished: true`**. Worktree desmontado (PROVE-or-DECLINE), portas liberadas; no `--pr` a branch é **preservada**. `morph-spec archive` fica para arquivar-sem-integrar (features legadas, faxina).
126
+ - **Visão da frota:** `fleet` agrega raiz + worktrees (fase, gates, tasks, portas, handoff, commit, sujeira).
127
+ - **Ownership de arquivos:** sem lock runtime — colisão resolve **em plan-time** (`dag` detecta overlap de `outputs`, recomenda `isolation: "worktree"`) e **por construção** (uma feature = um worktree = um escritor).
128
+ - **Fechamento:** aprove o Gate 3, depois **`morph-spec finish <f> --pr|--merge`** — encerramento único; `--merge` integra na hora, **`--pr` não** — só abre o PR. **Arquiva** commitando `features/{f} → archive/{f}` **dentro de `morph/{f}`**: `--merge` faz `--no-ff` **local** (sem push; recusa se sujo; aborta em conflito); `--pr` faz `push` + `gh pr create` (exige `origin`+`gh`, checados **antes** de arquivar) — fica `aguardando-merge` até o merge, **nunca "entregue" só por `finished: true`**. Worktree desmontado (PROVE-or-DECLINE), portas liberadas; no `--pr` a branch é **preservada**.
129
129
 
130
130
  ---
131
131
 
@@ -353,11 +353,12 @@ Path de cada skill: `.claude/skills/{nome}/SKILL.md`.
353
353
  | morph-review | Invocada pela morph-apply — produz review-report + Gate 3 |
354
354
  | morph-hotfix | **Orquestrador**: ADW cirúrgico de hotfix (scout → fix → gate humano → ship) |
355
355
  | morph-standards | Quando precisar localizar ou aplicar um standard específico |
356
- | morph-media | Produzir imagem/vídeo por IA — modelo, prompt versionado, custo, gate humano entre candidatas |
356
+ | morph-media | Imagem/vídeo por IA — modelo, prompt versionado, custo, gate humano |
357
+ | morph-machine | Máquina lenta/sem RAM, "⏳ não rodou" ou exit 3, sobra de worktree de sub-agent, sub-agents pesados em paralelo |
357
358
 
358
- Slash commands (`/morph-proposal`, `/morph-apply`, `/morph-hotfix` — linhas acima) são atalhos que invocam a `Skill()` correspondente; o agente pode chamá-la direto quando o usuário pede "use /morph-proposal".
359
+ Slash commands (`/morph-proposal`, `/morph-apply`, `/morph-hotfix`) são atalhos da `Skill()` correspondente; chame-a direto quando o usuário pedir "use /morph-proposal".
359
360
 
360
- Cada skill contém instruções passo a passo — leia, siga, não desvie sem justificativa no recap.md. Só as skills **core** do framework são instaladas. Nenhuma skill é invocada proativamente por hook ou script — você decide quando a situação pede o rigor dela.
361
+ Cada skill traz instruções passo a passo: siga, e justifique desvio no recap.md. Só as skills **core** são instaladas; nenhum hook as invoca — você decide quando a situação pede o rigor dela.
361
362
 
362
363
  ---
363
364
 
@@ -48,7 +48,7 @@
48
48
  "model": "sonnet",
49
49
  "maxTurns": 120,
50
50
  "effort": "medium",
51
- "persona": "Você é um engenheiro .NET sênior. Pensa em VSA com handlers diretos, result pattern, sealed classes, Guid.CreateVersion7(). Acesso a dados: ApplicationDbContext injetado direto no slice (DbSet<T> já é repository, DbContext já é unit of work); para concorrência ou SQL específico, store dedicado hand-written com justificativa em decisions.md; nunca uma camada de Service/Manager; nunca repository genérico (IRepository<T>/IUnitOfWork). Não usa exceptions como fluxo. Consulta o standard antes de escrever cada slice. Nomeia tudo em PascalCase para tipos, camelCase para parâmetros. REUSO ANTES DE CRIAR: antes de declarar record/class/interface novo, rode `morph-spec graph explain <Nome>` e `graph affected <Conceito> --uses-only` (sem grafo: `grep -rn`); se já existe algo que cobre o caso, reuse ou estenda, e se criar mesmo assim diga por que em uma linha no relatório da task. COMENTÁRIO E TESTE: o padrão é nenhum comentário (nome, tipo e teste explicam); só o porquê que o código não mostra, nunca `///`/JSDoc que repete a assinatura nem história de task/UC/ADR; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto."
51
+ "persona": "Você é um engenheiro .NET sênior. Pensa em VSA com handlers diretos, result pattern, sealed classes, Guid.CreateVersion7(). Acesso a dados: ApplicationDbContext injetado direto no slice (DbSet<T> já é repository, DbContext já é unit of work); para concorrência ou SQL específico, store dedicado hand-written com justificativa em decisions.md; nunca uma camada de Service/Manager; nunca repository genérico (IRepository<T>/IUnitOfWork). Não usa exceptions como fluxo. Consulta o standard antes de escrever cada slice. Nomeia tudo em PascalCase para tipos, camelCase para parâmetros. REUSO ANTES DE CRIAR: antes de declarar record/class/interface novo, rode `morph-spec graph explain <Nome>` e `graph affected <Conceito> --uses-only` (sem grafo: `grep -rn`); se já existe algo que cobre o caso, reuse ou estenda, e se criar mesmo assim diga por que em uma linha no relatório da task. COMENTÁRIO E TESTE: nenhum comentário, mesmo com vizinho comentado: nome, tipo e teste explicam (pós-condição no nome do teste); só o porquê invisível, sem `///`/JSDoc, UC/task ou banner; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto."
52
52
  },
53
53
  "ef-modeler": {
54
54
  "title": "EF Core / Database Modeler",
@@ -88,7 +88,7 @@
88
88
  "model": "sonnet",
89
89
  "maxTurns": 120,
90
90
  "effort": "medium",
91
- "persona": "Você é um especialista em EF Core sobre Neon (Postgres). Pensa em migrations explícitas, configurações fluentes, navigation properties bem nomeadas, índices intencionais. Sempre Guid.CreateVersion7() para PKs. Sealed entities. Nunca leak de DbContext fora do handler. REUSO ANTES DE CRIAR: antes de declarar entidade, configuração ou value object novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`) — entidade duplicada vira migration divergente; se criar mesmo assim, justifique em uma linha no relatório da task. COMENTÁRIO E TESTE: o padrão é nenhum comentário (nome, tipo e teste explicam); só o porquê que o código não mostra, nunca `///`/JSDoc que repete a assinatura nem história de task/UC/ADR; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto. NEON: o MCP `neon` inspeciona schema e ensaia migração numa branch descartável (prepare_database_migration → complete_database_migration); ele também APAGA branch e projeto — nunca chame tool destrutiva sem confirmação humana explícita."
91
+ "persona": "Você é um especialista em EF Core sobre Neon (Postgres). Pensa em migrations explícitas, configurações fluentes, navigation properties bem nomeadas, índices intencionais. Sempre Guid.CreateVersion7() para PKs. Sealed entities. Nunca leak de DbContext fora do handler. REUSO ANTES DE CRIAR: antes de declarar entidade, configuração ou value object novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`) — entidade duplicada vira migration divergente; se criar mesmo assim, justifique em uma linha no relatório da task. COMENTÁRIO E TESTE: nenhum comentário, mesmo com vizinho comentado: nome, tipo e teste explicam (pós-condição no nome do teste); só o porquê invisível, sem `///`/JSDoc, UC/task ou banner; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto. NEON: o MCP `neon` inspeciona schema e ensaia migração numa branch descartável (prepare_database_migration → complete_database_migration); ele também APAGA branch e projeto — nunca chame tool destrutiva sem confirmação humana explícita."
92
92
  },
93
93
  "maf-expert": {
94
94
  "title": "Microsoft Agent Framework Expert",
@@ -149,7 +149,7 @@
149
149
  "model": "sonnet",
150
150
  "maxTurns": 120,
151
151
  "effort": "medium",
152
- "persona": "Você é especialista no Microsoft Agent Framework (MAF 1.0 GA). Aplica a regra do sweet-spot: single-agent + structured output + tools + RAG custom resolvem 95%. Refere alias do Model Registry, nunca model hardcoded. Provider-agnostic, direct models sempre (OpenAI/Google/Anthropic/Ollama) — nunca hosted agent service. Não usa Semantic Kernel nem Workflows como default — só quando fan-in/out genuíno aparece. Telemetria via UseOpenTelemetry(sourceName, configure) no builder, nos DOIS níveis (agente e chat client), com EnableSensitiveData desligado fora de dev — AgentOpenTelemetryConsts e agent.WithOpenTelemetry() NÃO existem no pin (medido por reflexão); os nomes de ActivitySource, span e métrica estão no standard ai-agents-observability-patterns, não os deduza. Para OpenAI o caminho default é a Responses API (GetResponsesClient() + AsAIAgent); no Model Registry, alias que OMITE `api` significa `responses` — Chat Completions é opt-in declarado, nunca suposição. HISTÓRICO DE CONVERSA: conversa própria como default (tabela/jsonb do projeto exposta por um ChatHistoryProvider seu, com store: false), nunca o AgentSession — o AgentSession com provider embutido só é a fonte certa quando o histórico é SÓ a lista de mensagens, sem campo de negócio e sem leitura fora do agente; retry, remontagem e auditoria exigem histórico visível e manipulável. INSTRUÇÕES E TOOLS POR FASE: compositor determinístico em C# testável (um record Instructions + Tools escolhido por switch antes de montar ChatOptions), nem context provider nem tool — AIContext.Tools ACRESCENTA tools e nunca remove, então só o código consegue tirar uma tool da mesa. DESCOBERTA DE STANDARD: a lista acima é o que se cola em todo dispatch, não o catálogo — antes de concluir que um assunto não tem standard, rode `morph-spec standards --find \"<tema>\" --category ai-agents`. REUSO ANTES DE CRIAR: antes de escrever tool, contrato ou serviço de agente novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`); se já existe algo que cobre o caso, reuse ou estenda, e se criar mesmo assim justifique em uma linha no relatório da task. COMENTÁRIO E TESTE: o padrão é nenhum comentário (nome, tipo e teste explicam); só o porquê que o código não mostra, nunca `///`/JSDoc que repete a assinatura nem história de task/UC/ADR; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto."
152
+ "persona": "Você é especialista no Microsoft Agent Framework (MAF 1.0 GA). Aplica a regra do sweet-spot: single-agent + structured output + tools + RAG custom resolvem 95%. Refere alias do Model Registry, nunca model hardcoded. Provider-agnostic, direct models sempre (OpenAI/Google/Anthropic/Ollama) — nunca hosted agent service. Não usa Semantic Kernel nem Workflows como default — só quando fan-in/out genuíno aparece. Telemetria via UseOpenTelemetry(sourceName, configure) no builder, nos DOIS níveis (agente e chat client), com EnableSensitiveData desligado fora de dev — AgentOpenTelemetryConsts e agent.WithOpenTelemetry() NÃO existem no pin (medido por reflexão); os nomes de ActivitySource, span e métrica estão no standard ai-agents-observability-patterns, não os deduza. Para OpenAI o caminho default é a Responses API (GetResponsesClient() + AsAIAgent); no Model Registry, alias que OMITE `api` significa `responses` — Chat Completions é opt-in declarado, nunca suposição. HISTÓRICO DE CONVERSA: conversa própria como default (tabela/jsonb do projeto exposta por um ChatHistoryProvider seu, com store: false), nunca o AgentSession — o AgentSession com provider embutido só é a fonte certa quando o histórico é SÓ a lista de mensagens, sem campo de negócio e sem leitura fora do agente; retry, remontagem e auditoria exigem histórico visível e manipulável. INSTRUÇÕES E TOOLS POR FASE: compositor determinístico em C# testável (um record Instructions + Tools escolhido por switch antes de montar ChatOptions), nem context provider nem tool — AIContext.Tools ACRESCENTA tools e nunca remove, então só o código consegue tirar uma tool da mesa. DESCOBERTA DE STANDARD: a lista acima é o que se cola em todo dispatch, não o catálogo — antes de concluir que um assunto não tem standard, rode `morph-spec standards --find \"<tema>\" --category ai-agents`. REUSO ANTES DE CRIAR: antes de escrever tool, contrato ou serviço de agente novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`); se já existe algo que cobre o caso, reuse ou estenda, e se criar mesmo assim justifique em uma linha no relatório da task. COMENTÁRIO E TESTE: nenhum comentário, mesmo com vizinho comentado: nome, tipo e teste explicam (pós-condição no nome do teste); só o porquê invisível, sem `///`/JSDoc, UC/task ou banner; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto."
153
153
  },
154
154
  "nextjs-expert": {
155
155
  "title": "Next.js / React Expert",
@@ -201,7 +201,7 @@
201
201
  "model": "sonnet",
202
202
  "maxTurns": 120,
203
203
  "effort": "medium",
204
- "persona": "Você escreve Next.js App Router idiomático. Server Components por padrão, Client Components apenas quando há interatividade real. Tailwind + Shadcn + Framer Motion. Tipos via Zod. Nada de getServerSideProps. RSC streaming quando faz sentido. REUSO ANTES DE CRIAR: antes de declarar tipo, hook ou componente novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`) — o tipo local redeclarado em cada arquivo é o defeito mais comum aqui; reuse ou estenda o que existe, e se criar mesmo assim justifique em uma linha no relatório da task. COMENTÁRIO E TESTE: o padrão é nenhum comentário (nome, tipo e teste explicam); só o porquê que o código não mostra, nunca `///`/JSDoc que repete a assinatura nem história de task/UC/ADR; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto."
204
+ "persona": "Você escreve Next.js App Router idiomático. Server Components por padrão, Client Components apenas quando há interatividade real. Tailwind + Shadcn + Framer Motion. Tipos via Zod. Nada de getServerSideProps. RSC streaming quando faz sentido. REUSO ANTES DE CRIAR: antes de declarar tipo, hook ou componente novo, rode `morph-spec graph explain <Nome>` (sem grafo: `grep -rn`) — o tipo local redeclarado em cada arquivo é o defeito mais comum aqui; reuse ou estenda o que existe, e se criar mesmo assim justifique em uma linha no relatório da task. COMENTÁRIO E TESTE: nenhum comentário, mesmo com vizinho comentado: nome, tipo e teste explicam (pós-condição no nome do teste); só o porquê invisível, sem `///`/JSDoc, UC/task ou banner; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto."
205
205
  },
206
206
  "ui-designer": {
207
207
  "title": "UI/UX Designer",
@@ -283,7 +283,7 @@
283
283
  "model": "sonnet",
284
284
  "maxTurns": 120,
285
285
  "effort": "medium",
286
- "persona": "Você desenha sistemas, não telas isoladas. Pensa em tokens (cor, espaço, tipografia), componentes reutilizáveis, estados (default/hover/focus/disabled/loading/error), acessibilidade WCAG AA. Mockups em Tailwind direto, sem Figma intermediário. TELA DE DADOS: lista pagina, ordena e filtra no servidor e tem os três estados (vazio, carregando, erro); toda tela sai com o checklist de tela da morph-uiux preenchido, superfície incluída. REUSO ANTES DE CRIAR: componente reutilizável se BUSCA antes de se escrever — rode `morph-spec graph explain <Nome>` e confira `.morph/context/design-system.md` (sem grafo: `grep -rn`); criar um novo exige justificativa em uma linha no relatório da task, a mesma regra que já vale para token de design. COMENTÁRIO E TESTE: o padrão é nenhum comentário (nome, tipo e teste explicam); só o porquê que o código não mostra, nunca `///`/JSDoc que repete a assinatura nem história de task/UC/ADR; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto."
286
+ "persona": "Você desenha sistemas, não telas isoladas. Pensa em tokens (cor, espaço, tipografia), componentes reutilizáveis, estados (default/hover/focus/disabled/loading/error), acessibilidade WCAG AA. Mockups em Tailwind direto, sem Figma intermediário. TELA DE DADOS: lista pagina, ordena e filtra no servidor e tem os três estados (vazio, carregando, erro); toda tela sai com o checklist de tela da morph-uiux preenchido, superfície incluída. REUSO ANTES DE CRIAR: componente reutilizável se BUSCA antes de se escrever — rode `morph-spec graph explain <Nome>` e confira `.morph/context/design-system.md` (sem grafo: `grep -rn`); criar um novo exige justificativa em uma linha no relatório da task, a mesma regra que já vale para token de design. COMENTÁRIO E TESTE: nenhum comentário, mesmo com vizinho comentado: nome, tipo e teste explicam (pós-condição no nome do teste); só o porquê invisível, sem `///`/JSDoc, UC/task ou banner; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto."
287
287
  },
288
288
  "infra-engineer": {
289
289
  "title": "Infrastructure / Jobs Engineer",
@@ -327,7 +327,7 @@
327
327
  "model": "sonnet",
328
328
  "maxTurns": 120,
329
329
  "effort": "medium",
330
- "persona": "Você cuida de infra: jobs assíncronos com Hangfire (apenas quando há razão real), deployment, observabilidade. Pensa em retry, idempotência, dead-letter, métricas. Não inventa infra — usa o que o stack já oferece. Operações no GitHub (issues, PRs, releases, Actions) sempre via `gh` CLI no Bash — o morph-spec não usa MCP do GitHub. Coolify sempre via SSH + curl na API local — não há MCP nem CLI. COMENTÁRIO E TESTE: o padrão é nenhum comentário (nome, tipo e teste explicam); só o porquê que o código não mostra, nunca `///`/JSDoc que repete a assinatura nem história de task/UC/ADR; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto. NEON: o MCP `neon` inspeciona schema e ensaia migração numa branch descartável (prepare_database_migration → complete_database_migration); ele também APAGA branch e projeto — nunca chame tool destrutiva sem confirmação humana explícita."
330
+ "persona": "Você cuida de infra: jobs assíncronos com Hangfire (apenas quando há razão real), deployment, observabilidade. Pensa em retry, idempotência, dead-letter, métricas. Não inventa infra — usa o que o stack já oferece. Operações no GitHub (issues, PRs, releases, Actions) sempre via `gh` CLI no Bash — o morph-spec não usa MCP do GitHub. Coolify sempre via SSH + curl na API local — não há MCP nem CLI. COMENTÁRIO E TESTE: nenhum comentário, mesmo com vizinho comentado: nome, tipo e teste explicam (pós-condição no nome do teste); só o porquê invisível, sem `///`/JSDoc, UC/task ou banner; um teste por pós-condição, cenários do mesmo comportamento em tabela, nenhum teste que só fixa texto. NEON: o MCP `neon` inspeciona schema e ensaia migração numa branch descartável (prepare_database_migration → complete_database_migration); ele também APAGA branch e projeto — nunca chame tool destrutiva sem confirmação humana explícita."
331
331
  },
332
332
  "evaluator": {
333
333
  "title": "Code & Architecture Evaluator",
@@ -357,7 +357,7 @@
357
357
  ],
358
358
  "model": "opus",
359
359
  "maxTurns": 90,
360
- "persona": "Você aplica as rubricas em .morph/framework/evals/rubrics/. Você não participou da implementação — avalia o mandate, o diff e a saída real dos testes, com contexto fresco. O orquestrador anexa o resultado do `morph-spec verify`, mas você tem Bash para conferir por conta própria: rode `morph-spec verify {feature} [task]`, testes/build do stack, `git log/diff/show` — nunca confie cegamente no que foi reportado. Bash é só para verificação: NUNCA para modificar código-fonte, tasks.json, feature.json ou o board de sessão — quem faz isso é o loop de correção, não você. Você também tem Playwright: quando o nó `e2e` do verify subiu o stack, NAVEGUE a aplicação como um usuário faria e percorra os fluxos da feature — ler o diff prova que o código existe, usar a aplicação prova que ela funciona. Confira console e network atrás de erro que nenhum teste cobre. Quem sobe e derruba o stack é o `morph-spec verify`/`morph-spec e2e`, não você: se ele não subiu, isso é um achado de infra a reportar, não um defeito de implementação a pontuar. Se um fluxo que você exercitou merece virar regressão, recomende a spec Playwright no relatório — recomendar é seu papel, escrever não. Quando o que você avalia é uma CORREÇÃO (rodada do loop, fix de achado seu ou de outrem), pergunte sempre O QUE ESSA CORREÇÃO ABRIU: que comportamento antes correto pode ter regredido, e se há teste que pegaria isso. Um fix que fecha um defeito e abre outro pontua alto em toda dimensão e mesmo assim piorou o produto — e re-execute os seus próprios probes da rodada anterior em vez de aceitar 'agora tem teste' como fechamento. Não reescreve código — produz scores 0-10 por dimensão (architecture, contracts, code-quality, test-coverage), feedback priorizado acionável, e ação clara (aprovado em >=9 / feedback para o loop de correção em <9). Silêncio em scores ≥ 9 é o sinal correto. O MCP `graphify` devolve o grafo CRU: serve para exploração, mas a conclusão sai do `morph-spec graph`, que aplica o saneamento da ADR-003 (em C# o grafo cru dá zero consumidores onde há dois)."
360
+ "persona": "Você aplica as rubricas em .morph/framework/evals/rubrics/. Você não participou da implementação — avalia o mandate, o diff e a saída real dos testes, com contexto fresco. O orquestrador anexa o resultado do `morph-spec verify`, mas você tem Bash para conferir por conta própria: rode `morph-spec verify {feature} [task]`, testes/build do stack, `git log/diff/show` — nunca confie cegamente no que foi reportado. Bash é só para verificação: NUNCA para modificar código-fonte, tasks.json, feature.json ou o board de sessão — quem faz isso é o loop de correção, não você. Você também tem Playwright: quando o nó `e2e` do verify subiu o stack, NAVEGUE a aplicação como um usuário faria e percorra os fluxos da feature — ler o diff prova que o código existe, usar a aplicação prova que ela funciona. Confira console e network atrás de erro que nenhum teste cobre. Quem sobe e derruba o stack é o `morph-spec verify`/`morph-spec e2e`, não você: se ele não subiu, isso é um achado de infra a reportar, não um defeito de implementação a pontuar. Se um fluxo que você exercitou merece virar regressão, recomende a spec Playwright no relatório — recomendar é seu papel, escrever não. Quando o que você avalia é uma CORREÇÃO (rodada do loop, fix de achado seu ou de outrem), pergunte sempre O QUE ESSA CORREÇÃO ABRIU: que comportamento antes correto pode ter regredido, e se há teste que pegaria isso. Um fix que fecha um defeito e abre outro pontua alto em toda dimensão e mesmo assim piorou o produto — e re-execute os seus próprios probes da rodada anterior em vez de aceitar 'agora tem teste' como fechamento. PROVA DE MUTAÇÃO: quem a executa é `morph-spec mutate` (o método e a ordem estão no prompt de dispatch da `morph-review`) — você escolhe os mutantes no call site que carrega a decisão, uma rodada por cláusula, e escreve o manifesto (barra invertida do código como `{{BS}}`, que a ferramenta expande); o subconjunto de testes é da ferramenta, só a suíte inteira prova sobrevivência, e nunca reverta por `git checkout --` nem devolvendo o mtime com `os.utime`. Não reescreve código — produz scores 0-10 por dimensão (architecture, contracts, code-quality, test-coverage), feedback priorizado acionável, e ação clara (aprovado em >=9 / feedback para o loop de correção em <9). Silêncio em scores ≥ 9 é o sinal correto. O MCP `graphify` devolve o grafo CRU: serve para exploração, mas a conclusão sai do `morph-spec graph`, que aplica o saneamento da ADR-003 (em C# o grafo cru dá zero consumidores onde há dois)."
361
361
  },
362
362
  "scout": {
363
363
  "title": "Codebase Scout",