@polymorphism-tech/morph-spec 8.42.0 → 8.44.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 (101) hide show
  1. package/CLAUDE.md +15 -5
  2. package/README.md +2 -2
  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 +5 -4
  8. package/framework/MORPH.md +10 -9
  9. package/framework/agents.json +1 -1
  10. package/framework/docs/CLI.md +22 -10
  11. package/framework/evals/proof-rubrics/mutation-proof.md +43 -13
  12. package/framework/hooks/claude-code/core/heavy-gate.js +72 -0
  13. package/framework/hooks/claude-code/core/heavy-release.js +34 -0
  14. package/framework/hooks/claude-code/core/worktree-context.js +21 -20
  15. package/framework/hooks/claude-code/core/worktree-create.js +4 -4
  16. package/framework/hooks/claude-code/core/worktree-env-inject.js +1 -4
  17. package/framework/hooks/shared/agent-reap.js +89 -0
  18. package/framework/hooks/shared/e2e-check.js +3 -1
  19. package/framework/hooks/shared/graph-refresh.js +1 -9
  20. package/framework/hooks/shared/heavy-command.js +128 -0
  21. package/framework/hooks/shared/heavy-config.js +65 -0
  22. package/framework/hooks/shared/heavy-holders.js +85 -0
  23. package/framework/hooks/shared/heavy-slots.js +207 -0
  24. package/framework/hooks/shared/process-alive.js +8 -0
  25. package/framework/hooks/shared/worktree-root.js +93 -0
  26. package/framework/rules/ai-agents-standards.md +1 -0
  27. package/framework/schemas/config.schema.json +80 -0
  28. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  29. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  30. package/framework/skills/core/morph-apply/SKILL.md +8 -3
  31. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  32. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  33. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  34. package/framework/skills/core/morph-implement/SKILL.md +9 -4
  35. package/framework/skills/core/morph-machine/SKILL.md +46 -0
  36. package/framework/skills/core/morph-media/SKILL.md +1 -1
  37. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  38. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  39. package/framework/skills/core/morph-review/SKILL.md +45 -16
  40. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  41. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  42. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  43. package/framework/standards/PROVENANCE.json +10 -10
  44. package/framework/standards/STANDARDS.json +45 -1
  45. package/framework/standards/ai-agents/tool-adapters.md +226 -0
  46. package/framework/standards/frontend/design-system/ai-video-generation.md +2 -0
  47. package/framework/standards/frontend/scroll-driven/frame-scrub.md +11 -1
  48. package/framework/standards/infrastructure/docker/local-compose-isolation.md +11 -1
  49. package/package.json +1 -1
  50. package/src/commands/e2e/e2e.js +27 -8
  51. package/src/commands/mcp/mcp-setup.js +10 -9
  52. package/src/commands/project/doctor-procs.js +162 -0
  53. package/src/commands/project/doctor.js +42 -4
  54. package/src/commands/project/heavy.js +61 -0
  55. package/src/commands/project/init.js +1 -1
  56. package/src/commands/project/update.js +4 -0
  57. package/src/commands/project/worktree-reap.js +34 -0
  58. package/src/commands/project/worktree.js +112 -43
  59. package/src/commands/state/approve.js +2 -1
  60. package/src/commands/state/finish.js +4 -0
  61. package/src/commands/state/gate-check.js +2 -1
  62. package/src/commands/validation/mutate.js +200 -0
  63. package/src/commands/validation/verify.js +39 -22
  64. package/src/dashboard/scanner/features-scanner.js +4 -4
  65. package/src/dashboard/scanner/project-api.js +4 -4
  66. package/src/lib/detectors/claude-config-detector.js +30 -1
  67. package/src/lib/e2e/compose-env.js +5 -2
  68. package/src/lib/git/evidence-staleness.js +39 -0
  69. package/src/lib/heavy/cleanup.js +70 -0
  70. package/src/lib/heavy/gate.js +96 -0
  71. package/src/lib/installers/mcp-installer.js +15 -2
  72. package/src/lib/mutation/adapters.js +90 -0
  73. package/src/lib/mutation/commands.js +60 -0
  74. package/src/lib/mutation/manifest.js +138 -0
  75. package/src/lib/mutation/plan.js +36 -0
  76. package/src/lib/mutation/report.js +66 -0
  77. package/src/lib/mutation/runner.js +351 -0
  78. package/src/lib/mutation/schemata.js +60 -0
  79. package/src/lib/mutation/snapshot.js +69 -0
  80. package/src/lib/mutation/subset.js +75 -0
  81. package/src/lib/mutation/trx.js +51 -0
  82. package/src/lib/procs/classify.js +91 -0
  83. package/src/lib/procs/list.js +124 -0
  84. package/src/lib/procs/mcp-config.js +103 -0
  85. package/src/lib/procs/prune.js +25 -0
  86. package/src/lib/procs/wslconfig.js +70 -0
  87. package/src/lib/projects/registry.js +6 -2
  88. package/src/lib/risk-detector.js +3 -3
  89. package/src/lib/tasks/eval-node.js +4 -34
  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/contracts/contract-compliance-validator.js +4 -42
  93. package/src/lib/validators/verify-runner.js +26 -8
  94. package/src/lib/worktree/agent-reaper.js +247 -0
  95. package/src/lib/worktree/dirs.js +3 -32
  96. package/src/lib/worktree/env-allocator.js +35 -5
  97. package/src/lib/worktree/vscode-settings.js +113 -0
  98. package/src/lib/worktree/worktree-audit.js +49 -5
  99. package/src/lib/worktree/worktree-include.js +46 -0
  100. package/src/scripts/setup-infra.js +4 -0
  101. package/src/utils/hooks-installer.js +11 -4
package/CLAUDE.md CHANGED
@@ -137,11 +137,13 @@ When modifying framework components, verify the full scope of related files.
137
137
  | `framework/MORPH.md` | All core skills (cross-references), CLAUDE.md (slash command list), e o **teto de 38 400 chars** (`test/framework/resident-size.test.js`). MORPH.md vive colado no teto: linha nova ali só entra liberando pelo menos o mesmo número de caracteres **na mesma seção**, com o antes/depois medido — `MORPH_MD_LIMIT` não sobe |
138
138
  | a divisão loop × suíte (`verify {f} {task}` recorta sozinho; `--filter` força nós, `--full` desliga) | os QUATRO textos que a declaram e não podem discordar: `framework/docs/CLI.md` (seção "Suítes de teste" — a tabela canônica, a forma de `project.tests[]` e a subseção *O recorte por task*, com os limites), `morph-implement` §9, `morph-apply` §3a (o `verify` do loop e os `outputs` gravados ANTES dele — o recorte os lê) e `morph-review` (o Gate 3 roda **sem** filtro e `verify {feature}` nunca recorta). Uma superfície que ensine filtrar no Gate 3 devolve o F30 pela porta da frente; canário: `test/framework/verify-stack-docs-prose.test.js` |
139
139
  | `src/lib/worktree/ignored-triage.js` | `worktree.js` (`classifyIgnoredLeftovers`), `test/lib/worktree/ignored-triage.test.js`, e os canários de `test/commands/finish/merge-worktree.test.js` (e o pré-voo de `finish/dry-run.test.js`) que pinam "o exemplar único nunca morre" — mudar um bucket muda o que o `finish` apaga |
140
- | `src/lib/worktree/finish-backup.js` (backups do finish e a retenção) | os QUATRO pontos que escrevem ou leem `.git/morph-spec/finish-backup/`: `finish.js` (o shed da raiz e a poda do fim, `enforceBackupRetention`), `worktree.js` (o backup do desmonte e a poda do fim do `worktree remove`), `worktree-audit.js` (`auditFinishBackups` → `doctor --worktrees`) e `doctor.js` (a linha `.git/morph-spec` do padrão, nunca `✗`, e o `--prune`, a ÚNICA poda que inclui legado). `resolveBackupRetention` é a autoridade do `finishBackup.retention` de `framework/schemas/config.schema.json`: política que não se lê PULA a poda, nunca cai no padrão, porque a operação apaga. Backup sem manifesto é legado, e a poda automática nunca o apaga nem o conta. Um finish grava as duas pastas com o MESMO carimbo, e o carimbo da execução vai em `protectStamps`. Só `task/` é namespace. Apagar é PROVE-or-DECLINE, como a triagem de `ignored-triage.js` (que decide o que entra no backup) |
140
+ | `src/lib/worktree/finish-backup.js` (backups do finish e a retenção) | os QUATRO pontos que escrevem ou leem `.git/morph-spec/finish-backup/`: `finish.js` (o shed da raiz e a poda do fim, `enforceBackupRetention`), `worktree.js` (o backup do desmonte, o shed do esqueleto da raiz de feature já arquivada — `clearRootWorktreeRegistration`, que só commita registro RASTREADO — e a poda do fim do `worktree remove`), `worktree-audit.js` (`auditFinishBackups` → `doctor --worktrees`) e `doctor.js` (a linha `.git/morph-spec` do padrão, nunca `✗`, e o `--prune`, a ÚNICA poda que inclui legado). `resolveBackupRetention` é a autoridade do `finishBackup.retention` de `framework/schemas/config.schema.json`: política que não se lê PULA a poda, nunca cai no padrão, porque a operação apaga. Backup sem manifesto é legado, e a poda automática nunca o apaga nem o conta. Um finish grava as duas pastas com o MESMO carimbo, e o carimbo da execução vai em `protectStamps`. Só `task/` é namespace. Apagar é PROVE-or-DECLINE, como a triagem de `ignored-triage.js` (que decide o que entra no backup) |
141
+ | a nota `memoryNote` do `finish` (`MEMORY_NOTE` em `finish.js`) | os DOIS leitores da mesma frase: `framework/docs/CLI.md` (entrada do `finish`) e `test/commands/finish/memory-note.test.js`. A nota é carimbada no **`emit`**, ponto único por onde passa todo `finished: true`; o canário do `--pr` existe por isso: aquele payload é montado À MÃO, fora do `mergeOutput`, então mover a nota para o construtor do merge deixa o teste do `--merge` verde e só ESSE morde (medido por mutação). A frase nomeia o que o repositório NÃO guarda; medir só a presença do campo aprovaria uma nota vazia |
141
142
  | `src/lib/git/pr-index.js` | os QUATRO leitores do mesmo fato "esta branch tem PR": `fleet-scanner.js` (coluna PR), `branch-classifier.js` (o que NÃO apagar), `worktree-audit.js` (o que NÃO sugerir remover) e `pending-prs.js` (a síntese "aguardando merge"). `null` é UNKNOWN, nunca "sem PR" |
142
143
  | `src/lib/git/branch-classifier.js` | `src/commands/project/branch.js`, `worktree-audit.js` (`unintegratedWork` reusa `classifyBranch` — o `doctor` e o `branch prune` não podem discordar sobre a mesma branch), `test/commands/branch-prune.test.js`, e `lifecycle-audit.js` (`abandoned-scaffold` conta commit próprio pelo mesmo `resolveBaseRef` + `commitsAhead`) |
143
144
  | `src/lib/git/pending-prs.js` | os TRÊS consumidores da mesma síntese "aguardando merge": `list.js` (linha sintetizada), `fleet-scanner.js` (entrada `pr-pending`) e `doctor.js` (`auditPullRequests`). Uma feature visível numa superfície e invisível na outra é o bug que este módulo existe para impedir |
144
145
  | `src/lib/git/gh.js` | `pr-index.js`, `pr-files.js` e `finish.js` — as DUAS formas de invocar o `gh` viraram uma; uma segunda cópia de `resolveGh` volta a tornar o caminho de sucesso do índice intestável |
146
+ | `src/lib/git/evidence-staleness.js` (`treeMovedSince`, "a árvore andou desde o commit da prova") | os DOIS leitores do mesmo fato: `gate-check.js` (`verificationStale`, o carimbo do `verify`) e `approve.js` (`reviewStaleness`, o `Reviewed-SHA` do review-report). Uma régua só: comparar SHA com HEAD fazia todo commit de estado (o `feature.json` do próprio verify, `tasks.json`, `recap.md`) invalidar a prova sem nenhum byte de código diferente. Conta tudo fora de `.morph/` e, dentro dele, só o `config.json` — sem `expectedCount`, que a rodada de baseline escreve e não é evidência; um comando de teste trocado conta. `--no-renames` é load-bearing: um arquivo movido para dentro de `.morph/` aparece como criação lá e a deleção fora some. SHA que o git não resolve = andou (a resposta de antes); sem SHA ou sem HEAD = não andou (fail-open). **Não reuse `treeFingerprint` para isto:** ele hasheia o `HEAD`, então um commit só de `.morph/` o muda. Canários: `test/lib/git/evidence-staleness.test.js` (mutação: sem a normalização e sem `--no-renames` cada um derruba um teste) e os de `gate-check.test.js`/`approve.test.js` |
145
147
  | `src/lib/git/tree-fingerprint.js` (a impressão da árvore) | `verify.js` (grava `fingerprint` no evento `Verify`; `findRepeatedVerify`, de `verify-event.js`, compara com o último verify de feature COMPLETO — rodada com `persisted: false` nunca é a prova que uma completa substitui) e `metrics.js` (`verify.unchangedRepeats`, pela mesma regra). `.morph/` fica fora, exceto `config/config.json`: o próprio verify escreve feature.json e logs a cada rodada, e contá-los faria duas rodadas sobre o mesmo código parecerem diferentes. `repeatOf` é informação e nenhum gate o lê: ligá-lo ao Gate 3 é aceitar a prova de uma rodada anterior no lugar da atual |
146
148
  | `src/lib/git/tree-state.js` (o bloco `tree` do `report-check`) | `report-check.js` (toda saída, inclusive o batch `report-check <f> T1 T2`, e a foto `--before-eval` em `4-review/tree-before-eval.json`) e as skills que leem os campos: `morph-implement`/`morph-apply` (pós-dispatch e retomada do PARCIAL) e `morph-review` (higiene contra `tree.sinceBeforeEval`). Campo renomeado aqui sem as skills é instrução que lê `undefined`: `test/framework/report-check-wiring.test.js` confere que todo `tree.*` citado existe na saída real. A foto compara CONTEÚDO (blob por arquivo), não o `git status`: a mutação que o avaliador commitou também aparece. Fora de git o bloco é `null`, nunca árvore vazia. `MUTATION_MARKER` tem a mesma grafia que as skills mandam gravar |
147
149
  | `src/lib/worktree/feature-truth.js` | The six readers of the same "which tree owns this feature" predicate: `fleet-scanner.js`, `list.js`, `handoff.js`, the mutator guard (`approve.js`/`advance.js`/`score.js`/`gate-decision.js`/`gate-check.js`), `verify.js` (`decidePersistence`, asked from the PRIMARY ROOT — asked from cwd, step 4 would make any scratch tree carrying a `feature.json` persist) and the dashboard's `features-scanner.js` (only for ACTIVE features — it spawns git — and a worktree owner without the feature files is shown as `rootStale`, never silently replaced by the root skeleton) |
@@ -149,9 +151,10 @@ When modifying framework components, verify the full scope of related files.
149
151
  | `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 |
150
152
  | `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` |
151
153
  | `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) |
153
- | `--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 |
154
- | `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
+ | `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`). |
155
+ | `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 |
156
+ | `--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` (`baselineStampBlock`: só a rodada que GRAVOU a baseline persiste o stamp — sem gravação, o `reason` nomeia o nó), `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 |
157
+ | `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 se resolve em UM lugar, `resolveAiPin` (`src/lib/ai/pin-drift.js`, o mesmo do `doctor --ai`): `.morph/framework/ai-pin.json` do projeto e, sem ele, o do pacote da CLI. O `update` nunca copia o pin, então o fallback é o caso comum — um leitor próprio do eval deixou `MORPH_EVAL_PIN` sem ser injetado em todo projeto instalado, e com ele a régua do carimbo desligada sem aviso. Subir o pin do pacote reprova (`eval-cache-stale-pin`) toda cache carimbada no anterior: é o efeito, não um defeito. **`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` |
155
158
  | `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` |
156
159
  | `src/lib/graph/**` | `test/lib/graph/*.test.js`, the committed fixture `test/fixtures/graph/namespace-hierarchy.graph.json` (ADR-003 regression — never hand-edit it), `doctor.js` (`codebase graph` check), `architecture-scanner.js` (centrality section), `setup-infra.js` step 18b. `isTestPath` (`sanitize.js`) é o predicado ÚNICO de teste: `god-nodes.js`, `recoverDispatch`, `graph status\|explain` e `symbol-scanner.js` (`isTestFile`, que o `code-budget` também lê) o importam, e uma segunda régua faz o grafo e o validador discordarem sobre o mesmo arquivo. A fixture `test-doubles.graph.json` saiu do graphify real (passo a passo em `docs/specs/graphify-integration/fixture-test-doubles/`) — regerar, nunca editar à mão |
157
160
  | `framework/hooks/shared/graph-freshness.js` (a regra única de "grafo em dia") | os três lados que perguntam: `graph-refresh.js` (decide se roda e se o disparo solta processo), `graph-loader.js` `getFreshness` (`graph status\|explain\|affected` e o `doctor`) e `graph build\|refresh` (gravam a rodada em `<git-common-dir>/morph-spec/graph-refresh-state.json`). O registro da rodada existe porque o `graphify update` 0.9.50 não recarimba `built_at_commit` quando a topologia não muda: sem ele, o grafo parecia velho para sempre e cada disparo rodava o graphify de novo (~80 s no MORPH_GHLBrain). As regras do `.graphifyignore` entram por CONTEÚDO (hash sem comentário, BOM e CRLF), nunca pelo relógio, e `ignoreTextOf` é a ÚNICA leitura de "as regras desta árvore": numa worktree sem arquivo próprio, as da raiz — as mesmas que o `graph refresh` põe ali durante a rodada (`prepareIgnore` em `build.js`). O arquivo temporário leva `TEMP_IGNORE_MARKER` na 1ª linha; nas duas triagens de sujeira (`finish` e `worktree remove`) `withoutLeftoverIgnore` o tira da conta ANTES das recusas e `dropLeftoverIgnore` o apaga só DEPOIS delas — apagar antes é mutação atrás de uma precondição. Mudar a grafia do marcador num lado só devolve o `worktree-dirty` de uma rodada morta no meio. Mora em `hooks/shared` porque hook não importa `src/` |
@@ -162,9 +165,16 @@ When modifying framework components, verify the full scope of related files.
162
165
  | `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 |
163
166
  | 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` |
164
167
  | `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` |
168
+ | `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` |
169
+ | `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 |
170
+ | `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 |
171
+ | `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` |
172
+ | `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` |
173
+ | `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) |
174
+ | `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` |
165
175
  | 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) |
166
176
  | `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` |
167
- | `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 |
177
+ | `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. **Alias por projeto** (`e2e.portEnvNames`, `resolvePortAliases`): é só outro NOME para as mesmas quatro portas, então não cria offset nem toca o template; entra pelo `portLayout` e por isso os dois escritores o levam, o `layoutKeys` do `compose-env.js` o protege do `.env.e2e` herdado, e o hook `worktree-env-inject` injeta toda chave do manifesto (menos `MORPH_FEATURE`) em vez de uma lista literal que divergiria |
168
178
  | `src/lib/tasks/test-nodes.js` | os DOIS leitores de `validateTestNodes` — `verify-runner.js` (fail-closed: nó inválido avermelha o gate) e `doctor.js` (`doctor --full`, camada 3 do contrato do spec §2.2: WARN, e **pula sem imprimir nada** sem `config.json` ou sem `project.tests`, senão quebra `test/commands/doctor.test.js:46`). Uma segunda noção de "nó válido" faz o mesmo config passar num lugar e falhar no outro. `morphConfigPath` deriva de `CONFIG_FILE_NAME` (`core/state/state-manager.js`), a mesma constante de `readMorphConfig` — o JSDoc promete "one spelling" e agora é literal. O campo `groups` (#87): `TASK_GROUPS` é o MESMO enum do `group` de `tasks.schema.json` e de `config.schema.json` (canário em `test/lib/tasks/test-nodes.test.js`), `RUNNER_DEFAULT_GROUPS` dá o default por runner e `effectiveGroups` é o que `test-scope.js` lê — `null` é nó sem grupo, nunca palpite. `discoverFrontendPackages` (a união do fallback de `resolveTestNodes` e o WARN do `doctor --full`) varre as pastas de `PACKAGE_LOCATIONS`, de `src/lib/detectors/index.js` — uma segunda lista de pastas é como dois detectores começam a discordar |
169
179
  | `src/lib/worktree/docker-audit.js` | `worktree-audit.js` (`auditWorktrees` concatena os achados e é quem PASSA as features conhecidas — um segundo scan aqui discordaria sobre o que é órfão), `doctor.js` (a classificação `info`/`warn` = não-falha em `doctorWorktreesCommand`; e `samePathCase`, a regra de caixa de path que o WARN de frontend do `doctor --full` e o `spelled` de `test-scope.js` reusam — uma segunda cópia dela foi removida), `env-allocator.js` (`allocatedFeatures` é read-only de propósito: `allocateEnvBlock` ESCREVE, e uma auditoria que aloca inventa o estado que reporta), `test/lib/worktree/docker-audit.test.js`. Nunca no `doctor --full` (D10): `test/commands/doctor.test.js:46` exige zero `✗` neste repo |
170
180
  | `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`) |
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.42.0
6
+ **Version:** 8.44.0
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -379,4 +379,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
379
379
 
380
380
  ---
381
381
 
382
- *morph-spec v8.42.0 by [Polymorphism Tech](https://polymorphism.tech)*
382
+ *morph-spec v8.44.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.42.0",
3
+ "version": "8.44.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.42.0 by Polymorphism Tech*
190
+ *morph-spec v8.44.0 by Polymorphism Tech*
@@ -249,4 +249,4 @@ morph-spec doctor
249
249
 
250
250
  ---
251
251
 
252
- *morph-spec v8.42.0 by Polymorphism Tech*
252
+ *morph-spec v8.44.0 by Polymorphism Tech*
@@ -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
 
@@ -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",