@polymorphism-tech/morph-spec 9.4.0 → 9.6.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.
- package/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/bin/morph-spec.js +1 -0
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +23 -33
- package/framework/agents.json +6 -7
- package/framework/docs/CLI.md +12 -8
- package/framework/hooks/README.md +2 -2
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/hooks/claude-code/core/comment-guard.js +1 -0
- package/framework/hooks/claude-code/core/denylist.json +63 -1
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +0 -8
- package/framework/hooks/claude-code/core/gate-guard.js +0 -8
- package/framework/hooks/claude-code/core/heavy-gate.js +5 -3
- package/framework/hooks/claude-code/core/loop-detect.js +0 -3
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +2 -1
- package/framework/hooks/claude-code/core/protect-spec-files.js +0 -7
- package/framework/hooks/claude-code/core/validate-completion.js +0 -4
- package/framework/hooks/claude-code/core/worktree-context.js +0 -2
- package/framework/hooks/claude-code/statusline.py +3 -44
- package/framework/hooks/shared/build-check.js +12 -19
- package/framework/hooks/shared/comment-rules.js +11 -1
- package/framework/hooks/shared/feature-locator.js +7 -5
- package/framework/hooks/shared/heavy-command.js +12 -0
- package/framework/hooks/shared/heavy-config.js +28 -1
- package/framework/hooks/shared/heavy-slots.js +41 -5
- package/framework/hooks/shared/hook-response.js +0 -4
- package/framework/hooks/shared/payload-utils.js +1 -35
- package/framework/hooks/shared/phase-utils.js +0 -12
- package/framework/hooks/shared/slice-progress.js +0 -2
- package/framework/hooks/shared/state-reader.js +0 -19
- package/framework/hooks/shared/telemetry-logger.js +1 -1
- package/framework/rules/frontend-standards.md +1 -2
- package/framework/rules/nextjs-standards.md +9 -8
- package/framework/schemas/config.schema.json +12 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +32 -29
- package/framework/standards/STANDARDS.meta.json +13 -15
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/frontend/admin-app/data-table.md +1 -3
- package/framework/standards/frontend/admin-app/page-structure.md +8 -4
- package/framework/standards/frontend/nextjs/app-router.md +3 -4
- package/framework/standards/frontend/nextjs/components.md +2 -15
- package/framework/standards/frontend/nextjs/data-fetching.md +2 -3
- package/framework/standards/frontend/nextjs/lint.md +141 -0
- package/framework/standards/frontend/nextjs/motion-patterns.md +2 -7
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -2
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +5 -7
- package/framework/standards/frontend/nextjs/project-structure.md +3 -2
- package/framework/standards/frontend/nextjs/state-management.md +6 -7
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +1 -0
- package/framework/templates/README.md +4 -4
- package/framework/templates/REGISTRY.json +21 -309
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +54 -56
- package/package.json +3 -2
- package/src/commands/e2e/e2e.js +3 -0
- package/src/commands/project/env.js +3 -3
- package/src/commands/project/heavy.js +10 -3
- package/src/commands/project/worktree.js +2 -1
- package/src/commands/state/finish.js +17 -3
- package/src/commands/validation/mutate.js +2 -2
- package/src/commands/validation/verify.js +3 -0
- package/src/core/paths/output-schema.js +0 -9
- package/src/core/state/feature-identity.js +0 -2
- package/src/core/state/state-manager.js +1 -169
- package/src/core/templates/template-renderer.js +0 -23
- package/src/dashboard/app/lib/format.js +0 -7
- package/src/dashboard/app/lib/phases.js +0 -7
- package/src/dashboard/scanner/activity-scanner.js +0 -4
- package/src/lib/ai/ai-project-scan.js +1 -6
- package/src/lib/e2e/compose-env.js +26 -3
- package/src/lib/graph/graphify-runtime.js +0 -1
- package/src/lib/heavy/gate.js +23 -4
- package/src/lib/heavy/live-stacks.js +64 -0
- package/src/lib/mutation/snapshot.js +1 -5
- package/src/lib/risk-detector.js +9 -6
- package/src/lib/stack/stack-profile.js +0 -4
- package/src/lib/stack/stack-scan.js +15 -4
- package/src/lib/tasks/eval-node.js +1 -7
- package/src/lib/validators/validation-runner.js +0 -43
- package/src/lib/validators/verify-runner.js +18 -2
- package/src/lib/worktree/docker-audit.js +7 -3
- package/src/lib/worktree/env-allocator.js +27 -5
- package/src/lib/worktree/ignored-triage.js +8 -0
- package/src/utils/agents-installer.js +12 -19
- package/src/utils/claude-settings-manager.js +0 -28
- package/src/utils/file-copier.js +0 -53
- package/framework/hooks/shared/activity-logger.js +0 -131
- package/framework/standards/frontend/design-system/naming.md +0 -68
- package/src/commands/validation/index.js +0 -2
- package/src/lib/validators/code-budget/index.js +0 -2
- package/src/lib/validators/contracts/index.js +0 -1
- package/src/lib/validators/nextjs/index.js +0 -1
- package/src/lib/validators/packages/index.js +0 -1
- package/src/lib/validators/reuse/index.js +0 -2
package/CLAUDE.md
CHANGED
|
@@ -18,7 +18,7 @@ npm install --prefix src/dashboard
|
|
|
18
18
|
# Dev loop: only the test files the current change can reach (vs the merge-base with main)
|
|
19
19
|
npm run test:changed # add -- --list to see the selection and why
|
|
20
20
|
|
|
21
|
-
# Run all tests (files in parallel, ~
|
|
21
|
+
# Run all tests (files in parallel, cpus-1 at a time; ~6 min on a 16-core Windows box, bound by process/git contention)
|
|
22
22
|
npm test
|
|
23
23
|
|
|
24
24
|
# A subtree or file (still parallel); --test-concurrency=1 to debug serially
|
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:** 9.
|
|
6
|
+
**Version:** 9.6.0
|
|
7
7
|
**Requires:** Node.js 22+, Claude Code
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -380,4 +380,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
|
|
|
380
380
|
|
|
381
381
|
---
|
|
382
382
|
|
|
383
|
-
*morph-spec v9.
|
|
383
|
+
*morph-spec v9.6.0 by [Polymorphism Tech](https://polymorphism.tech)*
|
package/bin/morph-spec.js
CHANGED
|
@@ -520,6 +520,7 @@ program
|
|
|
520
520
|
.description('Print the worktree\'s dedicated environment (port block + COMPOSE_PROJECT_NAME), shell-loadable: eval "$(morph-spec env)" outside Claude Code; feature defaults to the morph/{f} or morph-task/{n} branch of cwd')
|
|
521
521
|
.option('--json', 'Output as JSON')
|
|
522
522
|
.option('--powershell', 'Emit $env:K="V" lines instead of POSIX exports')
|
|
523
|
+
.option('--with-secrets', 'Also emit the e2e.auth.envFile credentials (omitted by default: stdout lands in transcripts); use inside eval "$(…)"')
|
|
523
524
|
.action((feature, options) => envCommand(feature, options));
|
|
524
525
|
|
|
525
526
|
program
|
package/claude-plugin.json
CHANGED
package/docs/CHEATSHEET.md
CHANGED
package/docs/QUICKSTART.md
CHANGED
package/framework/MORPH.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Leia uma vez por sessão (e de novo após `/compact`). Os comandos imprimem a própria mecânica (sinais, vereditos, próximo passo); referência completa em `.morph/framework/CLI.md`.
|
|
4
4
|
>
|
|
5
|
-
> **Depois de ler:**
|
|
5
|
+
> **Depois de ler:** `morph-spec next <f>` decide o passo do disco (fase, gates, relatórios, verify).
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -16,8 +16,8 @@ Entre gates, você opera sozinho: o dev valida, você executa.
|
|
|
16
16
|
| Ambiguidade de escopo coberta pelo plano | Resolve pelo lado conservador e registra no relatório |
|
|
17
17
|
| Ambiguidade bloqueante (requisito ausente ou contraditório) | Uma pergunta via `AskUserQuestion` |
|
|
18
18
|
| Cenário `Cn` sem evidência | Loop de correção com o feedback, como o verify vermelho, antes da review |
|
|
19
|
-
| `next` devolve `escalate`
|
|
20
|
-
| `verify` exit 3
|
|
19
|
+
| `next` devolve `escalate` | `AskUserQuestion` (aceitar/continuar/parar), o único ponto de pausa do loop; nunca uma 4ª tentativa por conta própria |
|
|
20
|
+
| `verify` exit 3 | fila cheia, não vermelho: no máximo uma chamada nova, nunca laço de espera |
|
|
21
21
|
|
|
22
22
|
**Regra de ouro:** dúvida que não bloqueia → resolva e registre; dúvida que bloqueia → uma pergunta, no máximo. Vale **depois** do gate do plano: na abertura, o **modo entrevista** (`morph-plan` §3) pergunta até fechar o entendimento, e esse é o comportamento desejado.
|
|
23
23
|
|
|
@@ -38,14 +38,7 @@ O harness conhece o stack. Só pergunte sobre tecnologia com ambiguidade real.
|
|
|
38
38
|
| Frontend | Next.js App Router + Tailwind + Framer Motion + Shadcn |
|
|
39
39
|
| Jobs async | Hangfire, só com razão real |
|
|
40
40
|
|
|
41
|
-
Padrões proibidos (
|
|
42
|
-
- `Guid.NewGuid()` → `Guid.CreateVersion7()`: o v7 é ordenado no tempo e não fragmenta o índice do Postgres.
|
|
43
|
-
- Classe não-`sealed` sem justificativa: herança aberta por acidente vira contrato que ninguém desenhou.
|
|
44
|
-
- Exception como fluxo de controle: o Result deixa o erro esperado no tipo, e a exception fica para o inesperado.
|
|
45
|
-
- Camada de Service/Manager entre handler e dados: no VSA o handler fala com o `ApplicationDbContext` direto; a camada só repassa chamada.
|
|
46
|
-
- Repository genérico (`IRepository<T>`/`IUnitOfWork`): `DbSet<T>` já é repository e `DbContext` já é unit of work.
|
|
47
|
-
- SQL cru (`FromSqlRaw`, `ExecuteQuery(string)`) sem ADR no `plan.md`: o LINQ é tipado e parametrizado; SQL específico ou concorrência vão para um store dedicado com o porquê no ADR.
|
|
48
|
-
- Redeclarar tipo/componente que já existe: duas definições divergem em silêncio. Antes de criar, `morph-spec graph explain <nome>` (sem grafo: `grep -rn`); criar exige `CRIAR` com `why` em `## Reuso` do `plan.md`.
|
|
41
|
+
Padrões proibidos (os standards detalham; o mecânico, o build e o lint cobram): exception como fluxo de controle (o Result põe o erro esperado no tipo); Service/Manager entre handler e dados; `IRepository<T>`/`IUnitOfWork` (`DbSet<T>` já é repository); SQL cru sem ADR; redeclarar tipo que já existe (antes, `morph-spec graph explain <nome>`, sem grafo `grep -rn`; criar exige `CRIAR` com `why` em `## Reuso`).
|
|
49
42
|
|
|
50
43
|
---
|
|
51
44
|
|
|
@@ -89,9 +82,7 @@ Toda feature nasce com `description` obrigatória e um `workType`: `morph-spec c
|
|
|
89
82
|
|
|
90
83
|
**Ramo uiux (critério objetivo):** feature/bug que cria ou altera superfície de UI (componente, página, tela, estilo, navegação) toma o ramo `1-design/` depois do gate do plano (`morph-spec advance {feature} --design`, também com o gate já aprovado pelo humano; nunca `1-design/` à mão); sem superfície de UI, direto para implement. Skill: `morph-uiux`. A `morph-plan` §6 decide uma vez; nenhum outro ponto redecide.
|
|
91
84
|
|
|
92
|
-
**
|
|
93
|
-
|
|
94
|
-
**Skip:** `chore`/`hotfix` nascem com o gate `plan` carimbado por política e começam em `implement`; escrevem um `2-plan/plan.md` curto (uma fatia; no hotfix, C1 = sintoma ausente, C2 = regressão) e rodam `plan-check`. Mudança: `morph-spec retype <f> <tipo>` (só antes do Gate 3; o esqueleto intocado do plano sai junto).
|
|
85
|
+
**Skip:** `chore`/`hotfix` começam em `implement` com um `plan.md` curto de uma fatia (hotfix: C1 = sintoma ausente, C2 = regressão) e `plan-check`; trocar o tipo: `morph-spec retype`.
|
|
95
86
|
|
|
96
87
|
**Branch:** toda feature nasce em `morph/{f}`, ramificada da default, com ou sem `--worktree`, e fecha igual nos dois com `morph-spec finish <f> --pr|--merge` (§2.2).
|
|
97
88
|
|
|
@@ -99,16 +90,15 @@ Toda feature nasce com `description` obrigatória e um `workType`: `morph-spec c
|
|
|
99
90
|
|
|
100
91
|
Features independentes rodam em paralelo, uma por worktree e uma instância de Claude Code por worktree: `create <f> --worktree` (ou `worktree setup <f>`) na branch `morph/{f}`.
|
|
101
92
|
|
|
102
|
-
- **Uma feature = um worktree = uma instância.**
|
|
103
|
-
- `feature.
|
|
104
|
-
- **Só o morph
|
|
105
|
-
- **
|
|
106
|
-
- **Ambiente dedicado:** o setup escreve `.morph/worktree.env` (`PORT`, `API_PORT`, `POSTGRES_PORT`, `COMPOSE_PROJECT_NAME=morph-{f}`) e cada worktree sobe o seu compose; o hook de Bash injeta as variáveis (fora do Claude, `eval "$(morph-spec env)"`). Porta fixa ou `container_name` no compose reprova o nó e2e. `finish` derruba o stack com volumes (`--keep-volumes` preserva). Build/testes/e2e disputam 2 slots por usuário (`heavy status`): exit 3 é fila cheia, não vermelho; fora do `verify`, `heavy run -- <cmd>`.
|
|
93
|
+
- **Uma feature = um worktree = uma instância.** `feature.json` viaja commitado em `morph/{f}`; a cópia da raiz é esqueleto.
|
|
94
|
+
- **Só o morph cria worktree:** `worktree setup <f>` (feature) ou `worktree provision <n>` (isolamento de fatia, branch `morph-task/<n>`). `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`) entra por junction; faltou → `worktree link --all`.
|
|
95
|
+
- **Só o morph remove worktree.** Qualquer outra remoção atravessa a junction e apaga o conteúdo real da raiz (os hooks barram as formas conhecidas; o explorador, não). Fechar = `morph-spec finish <f> --pr|--merge`; só desmontar = `morph-spec worktree remove <f>`. Sobra de sub-agent (`agent-<hex>`): `worktree reap` remove só a que parou limpa e sem commit exclusivo.
|
|
96
|
+
- **Ambiente dedicado:** o setup escreve `.morph/worktree.env` (`PORT`, `API_PORT`, `POSTGRES_PORT`, `COMPOSE_PROJECT_NAME=morph-{f}`) e cada worktree sobe o seu compose; o hook de Bash injeta as variáveis (fora do Claude, `eval "$(morph-spec env)"`). `finish` derruba o stack com volumes (`--keep-volumes` preserva). Fora do `verify`, build e teste pesados vão por `heavy run -- <cmd>`.
|
|
107
97
|
- **Migração destrutiva → integre antes de abrir irmãos:** os irmãos ramificam do schema velho e quebram no merge.
|
|
108
98
|
- **`node_modules` é junction compartilhada:** dependência mudada afeta todos os irmãos; integre antes de seguir em paralelo.
|
|
109
99
|
- **Handoff:** `.morph/features/{f}/handoff.md` (regenerável por `morph-spec handoff <f>`): leia antes de re-derivar contexto; nunca edite.
|
|
110
100
|
- **Ownership:** sem lock; colisão se resolve no plano (ondas do `plan-check`) e por construção (uma feature = um escritor). Visão agregada: `fleet`.
|
|
111
|
-
- **Fechamento:** aprove o Gate 3 e rode `morph-spec finish <f> --pr|--merge`.
|
|
101
|
+
- **Fechamento:** aprove o Gate 3 e rode `morph-spec finish <f> --pr|--merge`. **`--pr` não integra:** a feature fica `aguardando-merge` até alguém mergear, nunca "entregue" só por `finished: true`.
|
|
112
102
|
|
|
113
103
|
---
|
|
114
104
|
|
|
@@ -116,20 +106,20 @@ Features independentes rodam em paralelo, uma por worktree e uma instância de C
|
|
|
116
106
|
|
|
117
107
|
### Plan (`2-plan/`)
|
|
118
108
|
|
|
119
|
-
- `plan.md` — fonte única da feature
|
|
120
|
-
- `checks.json` —
|
|
109
|
+
- `plan.md` — fonte única da feature (esqueleto do `create`; chore/hotfix: você o cria), gramática em `morph-spec plan-check --help`. Lido uma vez por sessão e pós-`/compact`.
|
|
110
|
+
- `checks.json` — escrito só pelo `plan-check`; é a régua que o verificador prova item a item.
|
|
121
111
|
- `stack-scan.md` e `scout-map.md` (§6) — insumos do plano.
|
|
122
112
|
|
|
123
|
-
**Congelamento:** aprovado o gate do plano, o corpo do `plan.md` trava: quem implementa não afrouxa o critério que prova o trabalho.
|
|
113
|
+
**Congelamento:** aprovado o gate do plano, o corpo do `plan.md` trava: quem implementa não afrouxa o critério que prova o trabalho. Fato que o código desmente vai em `## Errata` no fim (append-only); escopo mudou → `morph-spec unapprove {f} plan`.
|
|
124
114
|
|
|
125
115
|
### Implement (`3-implement/`)
|
|
126
116
|
|
|
127
|
-
- `3-implement/reports/F{n}.md` — um por fatia (sub-agent, ou você se inline);
|
|
117
|
+
- `3-implement/reports/F{n}.md` — um por fatia (sub-agent, ou você se inline); formato em `morph-apply`.
|
|
128
118
|
- `3-implement/evidencias/{Cn}.md` — no cenário `prova: eval` (entrada, modelo, saída, veredito) ou `prova: manual` (passo e resultado).
|
|
129
119
|
|
|
130
120
|
### Review (`4-review/`)
|
|
131
121
|
|
|
132
|
-
- `verifier.md`, `reviewer.md`, `security.md` (só com caminho sensível) —
|
|
122
|
+
- `verifier.md`, `reviewer.md`, `security.md` (só com caminho sensível) — formato em `.morph/framework/evals/review-checklist.md`; vale o último veredito.
|
|
133
123
|
|
|
134
124
|
---
|
|
135
125
|
|
|
@@ -157,11 +147,11 @@ Em `trust: "auto"` (default, `.morph/config/config.json → trust`), um gate sem
|
|
|
157
147
|
| `designSystemDivergence` | fontes de design conflitantes (tokens, versão, fonte aprovada) |
|
|
158
148
|
| `standardsViolations` | plano ou código viola um padrão proibido (lista não vazia) |
|
|
159
149
|
|
|
160
|
-
Risco que nenhum sinal representa: `morph-spec gate-decision <feature> <gate> --pause <bool> --reason <txt>`.
|
|
150
|
+
Risco que nenhum sinal representa: `morph-spec gate-decision <feature> <gate> --pause <bool> --reason <txt>`. Sinais completos: CLI.md (`gate-check`).
|
|
161
151
|
|
|
162
|
-
**Gates por tipo:**
|
|
152
|
+
**Gates por tipo:** o Gate 3 vale para todos; em `hotfix` é sempre humano (o `approve` recusa `--mode auto`) e exige só `4-review/verifier.md`, escrito pelo orquestrador.
|
|
163
153
|
|
|
164
|
-
**Aprovar é sempre pela CLI, nunca editando o estado:** em `trust: auto`, `morph-spec approve <feature> <gate> --mode auto`, ou `morph-spec advance <feature>` (aprova e cria a pasta da próxima fase; `--design` toma o ramo uiux). No caminho humano, o hook `state-sync` grava a resposta
|
|
154
|
+
**Aprovar é sempre pela CLI, nunca editando o estado:** em `trust: auto`, `morph-spec approve <feature> <gate> --mode auto`, ou `morph-spec advance <feature>` (aprova e cria a pasta da próxima fase; `--design` toma o ramo uiux). No caminho humano, o hook `state-sync` grava a resposta "aprovado, prosseguir" da pergunta que nomeia a feature; outra resposta → `morph-spec approve`.
|
|
165
155
|
|
|
166
156
|
---
|
|
167
157
|
|
|
@@ -174,21 +164,21 @@ Trabalho pesado ou de outro domínio vai para um sub-agent com persona e standar
|
|
|
174
164
|
|
|
175
165
|
Inline vale para fatia pequena no domínio já carregado; inline numa fatia que dispara um gatilho exige uma linha de porquê no relatório. Não despache quando o overhead passa da economia ou a fatia muda arquitetura que você precisa validar antes.
|
|
176
166
|
|
|
177
|
-
**Mecanismo:** `subagent_type: "morph-{id}"` (ex.: `morph-dotnet-senior`); a persona é o system prompt, gerada de `agents.json` em `.claude/agents/`.
|
|
167
|
+
**Mecanismo:** `subagent_type: "morph-{id}"` (ex.: `morph-dotnet-senior`); a persona é o system prompt, gerada de `agents.json` em `.claude/agents/`. Tipo não registrado: `general-purpose` com o `persona` colado no prompt, registrado no relatório da fatia.
|
|
178
168
|
|
|
179
169
|
**Scout:** `morph-scout` localiza com `morph-spec graph affected|explain` e cai para grep sem grafo. Obrigatório no início de um hotfix, default no plano de feature grande ou brownfield (`morph-plan` §4). Devolve um mapa (Observado/Inferido) em `2-plan/scout-map.md`; rode `morph-spec scout-check <mapa> --json` antes de usá-lo. Reprovado → re-despache uma vez com as falhas; segunda reprovação → grep você mesmo e escale.
|
|
180
170
|
|
|
181
171
|
**O prompt leva só o que muda por fatia** (`morph-apply` §3): cenários que ela cobre, arquivos, standards, caminho do relatório. As cláusulas fixas de todo dispatch de implementador vivem no corpo gerado da persona (`## Contrato de dispatch`), o único texto que todo dispatch carrega. Não passe histórico da conversa nem saída de outras tarefas.
|
|
182
172
|
|
|
183
|
-
**O arquivo é o retorno; a última mensagem é cortesia
|
|
173
|
+
**O arquivo é o retorno; a última mensagem é cortesia** (pode não chegar): leia o relatório no caminho canônico.
|
|
184
174
|
|
|
185
175
|
### Protocolo de auditoria do relatório
|
|
186
176
|
|
|
187
|
-
Relatório ausente não prova que o agente falhou, e relatório presente não prova que ele está certo (um gravado cedo fica completo e velho). A pergunta é "o trabalho entrou, e é isto que ele diz?", respondida por evidência: `morph-spec report-check <feature> [F1 F2 …] --json` dá o veredito e a ação (`hint`) de cada fatia, e a tabela de ação está em `morph-apply` §5. `report-stale` e `work-landed-report-missing` nunca se resolvem re-despachando: o trabalho já entrou.
|
|
177
|
+
Relatório ausente não prova que o agente falhou, e relatório presente não prova que ele está certo (um gravado cedo fica completo e velho). A pergunta é "o trabalho entrou, e é isto que ele diz?", respondida por evidência: `morph-spec report-check <feature> [F1 F2 …] --json` dá o veredito e a ação (`hint`) de cada fatia, e a tabela de ação está em `morph-apply` §5. `report-stale` e `work-landed-report-missing` nunca se resolvem re-despachando: o trabalho já entrou.
|
|
188
178
|
|
|
189
|
-
**Teto de turnos:**
|
|
179
|
+
**Teto de turnos:** o corte é cego; relatório `PARCIAL` vira `report-incomplete` com ação `resume`: retome o mesmo agente, não carimbe DEGRADADO.
|
|
190
180
|
|
|
191
|
-
**Paralelo:** fluxos genuinamente independentes (backend ‖ frontend) vão ao mesmo tempo, um agente por fatia, até 4.
|
|
181
|
+
**Paralelo:** fluxos genuinamente independentes (backend ‖ frontend) vão ao mesmo tempo, um agente por fatia, até 4. As ondas saem do `plan-check`. Colisão que escapou do plano → a segunda fatia vai num worktree próprio, em rolling: a cada relatório o orquestrador integra por merge e despacha a próxima pronta (`morph-apply` §4). A feature só segue para a review com `verify` verde e cada cenário com evidência (§0).
|
|
192
182
|
|
|
193
183
|
---
|
|
194
184
|
|
package/framework/agents.json
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"model": "sonnet",
|
|
50
50
|
"maxTurns": 120,
|
|
51
51
|
"effort": "medium",
|
|
52
|
-
"persona": "Você é um engenheiro .NET sênior. Pensa em VSA com handlers diretos, result pattern. 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 ADR no plan.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.
|
|
52
|
+
"persona": "Você é um engenheiro .NET sênior. Pensa em VSA com handlers diretos, result pattern. 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 ADR no plan.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. COMENTÁRIO: só o porquê que o código não mostra, mesmo com vizinho comentado; a pós-condição vai no nome do teste."
|
|
53
53
|
},
|
|
54
54
|
"ef-modeler": {
|
|
55
55
|
"title": "EF Core / Database Modeler",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"model": "sonnet",
|
|
91
91
|
"maxTurns": 120,
|
|
92
92
|
"effort": "medium",
|
|
93
|
-
"persona": "Você é um especialista em EF Core sobre Neon (Postgres). Pensa em migrations explícitas, configurações fluentes, navigation properties bem nomeadas, índices intencionais. Nunca leak de DbContext fora do handler.
|
|
93
|
+
"persona": "Você é um especialista em EF Core sobre Neon (Postgres). Pensa em migrations explícitas, configurações fluentes, navigation properties bem nomeadas, índices intencionais. Nunca leak de DbContext fora do handler. Entidade duplicada vira migration divergente: o REUSO do contrato vale dobrado aqui. COMENTÁRIO: só o porquê que o código não mostra, mesmo com vizinho comentado; a pós-condição vai no nome do teste."
|
|
94
94
|
},
|
|
95
95
|
"maf-expert": {
|
|
96
96
|
"title": "Microsoft Agent Framework Expert",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"model": "sonnet",
|
|
153
153
|
"maxTurns": 120,
|
|
154
154
|
"effort": "medium",
|
|
155
|
-
"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`.
|
|
155
|
+
"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`. COMENTÁRIO: só o porquê que o código não mostra, mesmo com vizinho comentado; a pós-condição vai no nome do teste."
|
|
156
156
|
},
|
|
157
157
|
"nextjs-expert": {
|
|
158
158
|
"title": "Next.js / React Expert",
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
"model": "sonnet",
|
|
205
205
|
"maxTurns": 120,
|
|
206
206
|
"effort": "medium",
|
|
207
|
-
"persona": "Você escreve Next.js App Router idiomático. Server Components por padrão, Client Components apenas quando há interatividade real. Tailwind +
|
|
207
|
+
"persona": "Você escreve Next.js App Router idiomático. Server Components por padrão, Client Components apenas quando há interatividade real. Tailwind + shadcn + Motion. Tipos via Zod. RSC streaming quando faz sentido. Tipo local redeclarado em cada arquivo é o defeito mais comum aqui (REUSO do contrato). COMENTÁRIO: só o porquê que o código não mostra, mesmo com vizinho comentado; a pós-condição vai no nome do teste."
|
|
208
208
|
},
|
|
209
209
|
"ui-designer": {
|
|
210
210
|
"title": "UI/UX Designer",
|
|
@@ -244,7 +244,6 @@
|
|
|
244
244
|
"*"
|
|
245
245
|
],
|
|
246
246
|
"standards": [
|
|
247
|
-
"frontend-design-system-naming",
|
|
248
247
|
"frontend-design-system-art-direction",
|
|
249
248
|
"frontend-design-system-premium-finish",
|
|
250
249
|
"frontend-design-system-animations",
|
|
@@ -294,7 +293,7 @@
|
|
|
294
293
|
"model": "opus",
|
|
295
294
|
"maxTurns": 120,
|
|
296
295
|
"effort": "high",
|
|
297
|
-
"persona": "Você desenha sistemas E telas que alguém lembra. As duas réguas valem juntas: a de sistema (tokens de cor, espaço e tipografia, componentes reutilizáveis, estados default/hover/focus/disabled/loading/error, WCAG AA) e a estética, que é o que separa um design correto de um design aprovado. DIREÇÃO ANTES DE COMPONENTE: antes de qualquer componente, escreva em `1-design/direcao.md` o conceito em uma frase, a metáfora visual, o que torna a tela memorável e o que ela NÃO pode parecer (template shadcn cru, card branco centralizado, lista de ícones genérica). Tela nova ou momento de marca: explore 2 ou 3 direções distintas, recomende uma e justifique — o standard `frontend-design-system-art-direction` é a régua. SKILLS: carregue `frontend-design:frontend-design` sempre; `high-end-visual-design` em momento de marca; `redesign-existing-projects` em tela existente — cada uma só se instalada (a lista de skills da sessão diz); ausente, o standard de direção de arte é a régua e o relatório registra a ausência. CELULAR PRIMEIRO E NATIVO: folha inferior, zona do polegar, alvos de 44px, navegação que o polegar alcança; o celular é desenhado, nunca o desktop empilhado. MOVIMENTO COM INTENÇÃO: a ferramenta mais leve que entrega o efeito (CSS transition e `@keyframes` → animação por scroll em CSS →
|
|
296
|
+
"persona": "Você desenha sistemas E telas que alguém lembra. As duas réguas valem juntas: a de sistema (tokens de cor, espaço e tipografia, componentes reutilizáveis, estados default/hover/focus/disabled/loading/error, WCAG AA) e a estética, que é o que separa um design correto de um design aprovado. DIREÇÃO ANTES DE COMPONENTE: antes de qualquer componente, escreva em `1-design/direcao.md` o conceito em uma frase, a metáfora visual, o que torna a tela memorável e o que ela NÃO pode parecer (template shadcn cru, card branco centralizado, lista de ícones genérica). Tela nova ou momento de marca: explore 2 ou 3 direções distintas, recomende uma e justifique — o standard `frontend-design-system-art-direction` é a régua. SKILLS: carregue `frontend-design:frontend-design` sempre; `high-end-visual-design` em momento de marca; `redesign-existing-projects` em tela existente — cada uma só se instalada (a lista de skills da sessão diz); ausente, o standard de direção de arte é a régua e o relatório registra a ausência. CELULAR PRIMEIRO E NATIVO: folha inferior, zona do polegar, alvos de 44px, navegação que o polegar alcança; o celular é desenhado, nunca o desktop empilhado. MOVIMENTO COM INTENÇÃO: a ferramenta mais leve que entrega o efeito (CSS transition e `@keyframes` → animação por scroll em CSS → Motion → GSAP com ScrollTrigger e timeline), orçamento de 60 fps, só `transform` e `opacity`, nada de layout thrash; `prefers-reduced-motion` tem versão estática digna, composta, nunca só a animação desligada. ACABAMENTO: camadas, luz (gradiente radial, trama discreta), vidro com moderação, sombra com a cor do tema, ritmo tipográfico (escala modular, tracking negativo no display), microinteração em foco, hover e envio. PROTÓTIPO E AUTOCRÍTICA: toda tela nova ou alterada vira `1-design/prototipos/{tela}.html` autossuficiente, com os tokens REAIS do projeto lidos do CSS de tokens (o `design-system.md` pode estar velho), desktop e celular lado a lado em moldura de aparelho, com controles animado, estático e pausar. Sirva por HTTP local (o Playwright MCP recusa `file:`), tire print em 1280 e em 390 (`{tela}-1280.png`, `{tela}-390.png` ao lado do HTML), avalie contra as duas réguas (hierarquia, contraste, ritmo, celular, \"parece template?\"), corrija e repita até passar; o relatório lista as rodadas e o que cada uma corrigiu. PROIBIDO: número ou dado inventado em tela real (cena ilustrativa não traz métrica), cópia de marca de terceiros, efeito que quebra contraste AA. 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. Componente reutilizável se busca antes de se escrever, também em `.morph/context/design-system.md` (REUSO do contrato). COMENTÁRIO: só o porquê que o código não mostra, mesmo com vizinho comentado; a pós-condição vai no nome do teste."
|
|
298
297
|
},
|
|
299
298
|
"infra-engineer": {
|
|
300
299
|
"title": "Infrastructure / Jobs Engineer",
|
|
@@ -338,7 +337,7 @@
|
|
|
338
337
|
"model": "sonnet",
|
|
339
338
|
"maxTurns": 120,
|
|
340
339
|
"effort": "medium",
|
|
341
|
-
"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
|
|
340
|
+
"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: só o porquê que o código não mostra, mesmo com vizinho comentado; a pós-condição vai no nome do teste."
|
|
342
341
|
},
|
|
343
342
|
"verifier": {
|
|
344
343
|
"title": "Gate 3 Verifier",
|
package/framework/docs/CLI.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
**`morph-spec approve <feature> <gate> --mode auto`** — aprova um gate (`plan`/`uiux`/`review`) em `trust: auto`.
|
|
12
12
|
|
|
13
|
-
**O Gate 3 exige os relatórios de review**: `4-review/verifier.md` e `reviewer.md`, mais `security.md` quando o diff toca caminho sensível (`securityReviewFor`) — ausente, PARCIAL ou sem `**Verdict:**`, o comando recusa (exit 1) nomeando o arquivo. A assinatura humana (`--approver`) aprova mesmo com `FIX` ou cenário não provado; a `auto` recalcula a pausa do `gate-check`, cobertura dos `Cn` incluída. `**Reviewed-SHA:**` é lido de todo relatório presente e o pior vence: o código andou depois dele → recusa; sem carimbo → aviso. Se o dispatch for impossível nesta sessão, aprove com `--self-assessed` — a degradação é gravada em `approvalGates.review.selfAssessed` e aparece marcada no `morph-spec list`. `--force` **não** dispensa esta regra (é só para ordem de gate).
|
|
13
|
+
**O Gate 3 exige os relatórios de review**: `4-review/verifier.md` e `reviewer.md`, mais `security.md` quando o diff toca caminho sensível fora de teste (`securityReviewFor`; spec, `e2e/` e `test-results/` não contam) — ausente, PARCIAL ou sem `**Verdict:**`, o comando recusa (exit 1) nomeando o arquivo. A assinatura humana (`--approver`) aprova mesmo com `FIX` ou cenário não provado; a `auto` recalcula a pausa do `gate-check`, cobertura dos `Cn` incluída. `**Reviewed-SHA:**` é lido de todo relatório presente e o pior vence: o código andou depois dele → recusa; sem carimbo → aviso. Se o dispatch for impossível nesta sessão, aprove com `--self-assessed` — a degradação é gravada em `approvalGates.review.selfAssessed` e aparece marcada no `morph-spec list`. `--force` **não** dispensa esta regra (é só para ordem de gate).
|
|
14
14
|
|
|
15
15
|
**O plano semeia `bin/`/`obj/`** (`plan-check` que grava o `checks.json`, `approve <f> plan`, `advance` saindo do plano e `advance --design` com o plano já aprovado — o `plan-check` cobre também o gate gravado pelo hook state-sync): num projeto .NET — `project.stack` com `dotnet`, fatia de `dotnet-senior`/`ef-modeler`/`maf-expert` no `checks.json` ou `.csproj`/`.sln`/`.slnx` na árvore (fora de `worktrees/`, `node_modules/`, `bin/`, `obj/`) — as duas linhas entram no `.gitignore` da árvore dona antes do primeiro código, e a saída as nomeia para irem no mesmo commit. O `create` aplica a mesma regra (`src/lib/stack/dotnet-build-ignores.js`); no greenfield só a fatia do plano existe para decidir.
|
|
16
16
|
|
|
@@ -124,7 +124,7 @@ Feature sem `workType` (pré-v8) é tratada como `feature`.
|
|
|
124
124
|
|
|
125
125
|
**Índice de ADRs:** no mesmo commit do archive, o `finish` lê o `2-plan/plan.md` da feature: os ADRs de `## Decisões` com `**Alcance:** projeto` entram em `.morph/specs/adrs/` (um arquivo por ADR, `{feature}--ADR-{nnn}.md` — features paralelas nunca tocam o mesmo arquivo), e a tabela `ADR | Mudança | Escopo` de `### Conflitos com o acervo` retira (total, o arquivo sai) ou estreita (parcial, `scope:` no arquivo) os ADRs revogados — forma em `morph-spec plan-check --help`; o `plan-check` avisa, enquanto o plano ainda muda, a linha que o `finish` não aplicaria. O payload traz `adrIndex: {added, revoked, narrowed}` e `revocationProblems: [{id, reason, message}]` (linha malformada ou ambígua, ADR fora do índice) — cada chave só aparece com conteúdo, e nenhum problema recusa o `finish`.
|
|
126
126
|
|
|
127
|
-
`--merge` faz merge `--no-ff` **local** na default (sem push); `--pr` faz `push` + `gh pr create` (exige `origin` + `gh`, checados **antes** de arquivar — um `--pr` recusado não deixa meio-estado). Desmonta o worktree (PROVE-or-DECLINE) e libera o bloco de portas; no `--pr` **preserva** a branch (o PR precisa dela) e fica em `morph/{f}`.
|
|
127
|
+
`--merge` faz merge `--no-ff` **local** na default (sem push); `--pr` faz `push` + `gh pr create` (exige `origin` + `gh`, checados **antes** de arquivar — um `--pr` recusado não deixa meio-estado). Saída de teste não rastreada (`test-results/`, `TestResults/`, `playwright-report/`, `blob-report/`) não conta como árvore suja: na raiz só é tolerada, na worktree sai junto com o desmonte; qualquer outro arquivo não commitado recusa. Desmonta o worktree (PROVE-or-DECLINE) e libera o bloco de portas; no `--pr` **preserva** a branch (o PR precisa dela) e fica em `morph/{f}`.
|
|
128
128
|
|
|
129
129
|
**Sessão ancorada no worktree → o desmonte é ADIADO** (`--merge` e `--pr`): quando a sessão do Claude Code que rodou o `finish` tem o próprio worktree como projeto (`CLAUDE_PROJECT_DIR` — exportado ao CLI como `MORPH_SESSION_PROJECT_DIR` pelo hook `worktree-env-inject`, porque o Bash tool não o exporta e `cd <raiz> && morph-spec finish` apaga o cwd), a integração acontece normalmente, mas o worktree fica: saída com `worktreeRemoved: false`, `teardownDeferred: "session-anchored"`, `worktree: {path, branch}` e um `hint` com `morph-spec worktree remove <f>` para rodar depois, de uma sessão aberta na raiz. O `--dry-run` já avisa (`teardownDeferred` no pré-voo).
|
|
130
130
|
|
|
@@ -158,7 +158,7 @@ O `.morph/state.json` do worktree é índice derivável: ele se refaz sozinho a
|
|
|
158
158
|
|
|
159
159
|
### env
|
|
160
160
|
|
|
161
|
-
**`morph-spec env [feature] [--json|--powershell]`** — imprime o bloco do worktree em forma shell-loadable (`eval "$(morph-spec env)"`; PowerShell: `morph-spec env --powershell | Invoke-Expression`). Feature vem do argumento ou da branch `morph/{f}`/`morph-task/{n}` do cwd. Deriva de `buildComposeEnv`, a mesma função do nó e2e — um shell e o stack nunca discordam. É o caminho para quem roda **fora** do Claude Code (terminal humano, CI, sub-agent sem hooks). As credenciais do arquivo apontado por `e2e.auth.envFile`
|
|
161
|
+
**`morph-spec env [feature] [--json|--powershell] [--with-secrets]`** — imprime o bloco do worktree em forma shell-loadable (`eval "$(morph-spec env)"`; PowerShell: `morph-spec env --powershell | Invoke-Expression`). Feature vem do argumento ou da branch `morph/{f}`/`morph-task/{n}` do cwd. Deriva de `buildComposeEnv`, a mesma função do nó e2e — um shell e o stack nunca discordam. É o caminho para quem roda **fora** do Claude Code (terminal humano, CI, sub-agent sem hooks). As credenciais do arquivo apontado por `e2e.auth.envFile` **só saem com `--with-secrets`** (o stdout vai parar no transcript; use a flag dentro de `eval "$(…)"`), e sem ela os nomes omitidos vão para stderr. Uma chave `*_PORT` desse arquivo que não bate com nenhuma porta do bloco é avisada em stderr com a sugestão de declará-la em `e2e.portEnvNames`. E **chave de layout de portas vinda de lá é ignorada** — o bloco alocado a esta worktree prevalece (um `.env.e2e` copiado de outra árvore carrega o bloco DELA). A chave ignorada é nomeada em **stderr**, nunca em stdout, para o `eval` do shell seguir limpo.
|
|
162
162
|
|
|
163
163
|
### worktree remove
|
|
164
164
|
|
|
@@ -282,7 +282,7 @@ Fail-open: uma poda que falha nunca derruba o comando. PROVE-or-DECLINE como o r
|
|
|
282
282
|
|
|
283
283
|
O nó `e2e` é o único que **executa** a aplicação: sobe o `docker compose` do projeto, espera todo serviço ficar saudável e roda as specs Playwright quando existem (sem specs, ainda vale como smoke — "a app sobe" já pega o serviço não registrado que derruba o startup e passa batido no build).
|
|
284
284
|
|
|
285
|
-
**Onde o nó `build` compila** (chaves de `project` em `.morph/config/config.json`, forma em `config.schema.json`): `project.stack` (tags unidas por `+`, ex. `dotnet+nextjs`) escolhe os passos; `project.backendPath` é a pasta da solução .NET e `project.frontendPath` a do frontend (`tsc`), ambas relativas à raiz. Sem `backendPath` e sem `.sln`/`.slnx`/`.csproj` na raiz, o build compila a **única** `.sln`/`.slnx` do repositório (senão o único `.csproj`), fora de `worktrees/`, `node_modules/`, `bin/` e `obj/` (`discoverDotnetSolution`, `test-runner.js`); nenhuma ou várias → o build falha e o `feedback[]` lista as candidatas e manda declarar `project.backendPath`.
|
|
285
|
+
**Onde o nó `build` compila** (chaves de `project` em `.morph/config/config.json`, forma em `config.schema.json`): `project.stack` (tags unidas por `+`, ex. `dotnet+nextjs`) escolhe os passos; `project.backendPath` é a pasta da solução .NET e `project.frontendPath` a do frontend (`tsc --noEmit` e, com `eslint.config.*` e o pacote `eslint` instalados, `eslint --quiet` só nos arquivos que a feature mudou contra a base), ambas relativas à raiz. Sem `backendPath` e sem `.sln`/`.slnx`/`.csproj` na raiz, o build compila a **única** `.sln`/`.slnx` do repositório (senão o único `.csproj`), fora de `worktrees/`, `node_modules/`, `bin/` e `obj/` (`discoverDotnetSolution`, `test-runner.js`); nenhuma ou várias → o build falha e o `feedback[]` lista as candidatas e manda declarar `project.backendPath`.
|
|
286
286
|
|
|
287
287
|
As specs são procuradas na raiz, em `project.frontendPath` e, sem ele, nas pastas de frontend conhecidas (as mesmas do detector, mais `web`). Se o nó cai para smoke enquanto existe um `playwright.config.*` não executado em pasta de primeiro nível, o `verify` avisa nomeando a pasta e `project.frontendPath`. A contagem de specs sai em `nodes.e2e.count` do `verify --json` (`null` quando não rodou ou o sumário é ilegível, nunca 0); ela não é persistida no stamp.
|
|
288
288
|
|
|
@@ -382,13 +382,17 @@ Isolamento entre worktrees: `COMPOSE_PROJECT_NAME=morph-{feature}` separa os con
|
|
|
382
382
|
|
|
383
383
|
`heavy status` é **somente leitura**: quem segura cada slot (pid, label, feature, nó, idade), a limpeza exclusiva do build-server se houver e a RAM livre.
|
|
384
384
|
|
|
385
|
-
**Espera:** slot cheio, ou RAM livre (`os.freemem()`) abaixo
|
|
385
|
+
**Espera:** slot cheio, ou RAM livre (`os.freemem()`) abaixo do piso do tipo do comando, espera até `heavy.waitTimeoutSec` (600) avisando em **stderr** a cada 30 s e nomeando quem segura (o `--json` mantém o stdout limpo).
|
|
386
|
+
|
|
387
|
+
**Piso de RAM por tipo** (`heavy.minFreeMemGbByKind`, padrão `unit` 1,5 · `build` 4 · `e2e` 4 GB): `unit` = vitest/jest, `npm|pnpm|yarn test`, `npm run test[:x]` e o nó `tests` do verify; `e2e` = playwright, `docker compose up`, script com `e2e` no nome, o nó `e2e` e o `e2e up`; `build` = o resto (`dotnet build|test|publish` — o `dotnet test` compila —, `next build`, `docker build`, `mutate` e comando desconhecido). O `heavy run` e o hook do `gateBash` classificam pelo comando; o `verify` toma o slot com o piso do primeiro nó e, antes de um nó mais pesado (tests → e2e), espera de novo só pela RAM segurando o slot — estourar ali também é exit 3, sem stamp. `heavy.minFreeMemGb` (4) virou o fallback: **definido explicitamente**, vale para todo tipo que `minFreeMemGbByKind` não lista (`0` continua desligando a checagem em tudo); sem ele, cada tipo usa o seu padrão.
|
|
388
|
+
|
|
389
|
+
**Ocupado por RAM** (`verify`, `e2e up`, `heavy run`): a mensagem nomeia o piso e a chave (`heavy.minFreeMemGbByKind.<tipo>`) e lista as stacks e2e de pé deste repo (`docker compose ls`, prefixo `morph-`, compose sob a raiz principal — de todas as worktrees). Feature já arquivada (`.morph/archive/<f>` sem `.morph/features/<f>`) vem com `morph-spec e2e down <f>`; stack sem feature, com `docker compose -p <nome> down`. Docker fora ou lento (10 s) = lista vazia, nunca erro.
|
|
386
390
|
|
|
387
391
|
**Estouro = exit 3 (`busy`), nunca vermelho:** `verify --json` sai `{ status: "busy", persisted: false, busy: { reason, holders, freeGb, waitedMs } }`, **sem stamp e sem evento `Verify`** (nada foi verificado, e fila não é gate verde nem vermelho); `e2e up` e `heavy run` também saem 3. Quem orquestra repete a chamada — nas skills, exit 3 não conta como tentativa e não dispara o loop de correção.
|
|
388
392
|
|
|
389
393
|
**Slot vago:** pid morto, ou idade acima de `heavy.maxHoldMinutes` (90, contra reuso de PID); Ctrl-C deixa o arquivo, que o pid morto cobre. Um filho herda o slot por `MORPH_HEAVY_LEASE` e nunca pede um segundo.
|
|
390
394
|
|
|
391
|
-
**Config** (`.morph/config/config.json`, bloco `heavy`, schema em `config.schema.json`): `enabled`, `slots`, `minFreeMemGb`, `waitTimeoutSec`, `maxHoldMinutes`, `shutdownBuildServer`, `disableNodeReuse`, `gateBash`; valor ilegível cai no default **com aviso** — o bloco nunca bloqueia trabalho por estar errado.
|
|
395
|
+
**Config** (`.morph/config/config.json`, bloco `heavy`, schema em `config.schema.json`): `enabled`, `slots`, `minFreeMemGb`, `minFreeMemGbByKind`, `waitTimeoutSec`, `maxHoldMinutes`, `shutdownBuildServer`, `disableNodeReuse`, `gateBash`; valor ilegível cai no default **com aviso** — o bloco nunca bloqueia trabalho por estar errado.
|
|
392
396
|
|
|
393
397
|
Env: `MORPH_HEAVY=off` desliga o semáforo (a suíte do próprio framework roda assim, para não enfileirar nos slots reais de quem a executa), `MORPH_HEAVY_SLOTS`, `MORPH_HEAVY_DIR`.
|
|
394
398
|
|
|
@@ -404,7 +408,7 @@ Env: `MORPH_HEAVY=off` desliga o semáforo (a suíte do próprio framework roda
|
|
|
404
408
|
|
|
405
409
|
**O que a vaga não cobre:** ela não tem processo vivo para vigiar (o hook sai na hora), então vence por **TTL** — o `timeout` da chamada, no máximo o da plataforma, mais 30 s — e uma chamada recusada por regra de permissão ou interrompida pelo usuário, que não dispara `Post*`, segura o slot até lá. O TTL conta desde o `PreToolUse`, que roda **antes** do prompt de permissão: se a aprovação demora mais que o `timeout` + 30 s, a vaga vence com o comando ainda por rodar e outra chamada pode tomá-la (sobreassinatura, do lado seguro). Chamadas de Bash de um mesmo lote paralelo rodam **em série** no Claude Code (medido na 2.1.285: o `Pre` da 2ª só rodou depois do `Post` da 1ª), então um agente não fica esperando uma vaga que só a própria chamada anterior liberaria.
|
|
406
410
|
|
|
407
|
-
`run_in_background`, PowerShell e comando fora da lista não são cobertos, e a cláusula
|
|
411
|
+
`run_in_background`, PowerShell e comando fora da lista não são cobertos, e a cláusula PROCESSOS do contrato de dispatch (`heavy run`) segue valendo para eles.
|
|
408
412
|
|
|
409
413
|
**Medido** com `claude -p` real e a chain instalada: fila cheia recusa após a espera e não mexe na vaga alheia; a vaga está presa durante a chamada e volta vazia depois, com exit 0 e com exit 1. Custo num Bash **leve**: ~1,5 ms na chain Pre (mediana 61,4 → 62,9 ms, medido intercalado) e ~0 na Post; os módulos do semáforo só carregam quando o comando é pesado.
|
|
410
414
|
|
|
@@ -500,7 +504,7 @@ Sem fatia na linha de comando, a saída traz `reviews: { verifier, reviewer, sec
|
|
|
500
504
|
|
|
501
505
|
### stack-scan
|
|
502
506
|
|
|
503
|
-
**`morph-spec stack-scan [<feature>] [--json]`** — varredura determinística do código contra `config.project.stack`: só os stacks configurados são julgados (sem `project.stack`, nenhuma regra roda e o resultado é `não auditado: project.stack ausente em .morph/config/config.json`, nunca "Em compliance" — exit 0; stack sem regras de varredura sai `não auditado` pelo mesmo motivo; código de um stack não configurado é só anotado). `dotnet`: TargetFramework abaixo de net10 e ORM/banco diferente (Dapper puro, SQL Server, MySQL) são **Bloqueante**; `Guid.NewGuid()
|
|
507
|
+
**`morph-spec stack-scan [<feature>] [--json]`** — varredura determinística do código contra `config.project.stack`: só os stacks configurados são julgados (sem `project.stack`, nenhuma regra roda e o resultado é `não auditado: project.stack ausente em .morph/config/config.json`, nunca "Em compliance" — exit 0; stack sem regras de varredura sai `não auditado` pelo mesmo motivo; código de um stack não configurado é só anotado). `dotnet`: TargetFramework abaixo de net10 e ORM/banco diferente (Dapper puro, SQL Server, MySQL) são **Bloqueante**; `Guid.NewGuid()` (só sem os analyzers: com eles o build já barra), repository genérico (`IRepository<T>`/`IUnitOfWork` — código morto: apagar a pasta `Repository/` e as 2 linhas de DI), classe `*Service`/`*Manager` sobre `DbContext` e SQL cru (`FromSqlRaw`, `ExecuteSqlRaw`, `SqlQueryRaw`, `ExecuteQuery`), Semantic Kernel (pacote `Microsoft.SemanticKernel*` ou `[KernelFunction]` no código) e serviço de agente hospedado (`Azure.AI.Projects`, `Azure.AI.Agents.Persistent`) são **Alto** — os dois últimos são linhas da tabela de anti-padrões do `ai-agents-setup` §7, para a qual a migração aponta; projeto .NET sem `Microsoft.CodeAnalysis.BannedApiAnalyzers` (migração: `backend-dotnet-analyzers`) e SDK de IA (`Microsoft.Agents.AI*`, `Microsoft.Extensions.AI*`, `OpenAI`, `Google.GenAI`, `ModelContextProtocol`) com versão flutuante (`*` ou faixa) são **Médio**; pacote `Microsoft.EntityFrameworkCore|AspNetCore|Extensions` abaixo da major 10 é **Baixo**. `nextjs`: frontend sem Next.js é Bloqueante; Pages Router sem `app/`, biblioteca de UI diferente (MUI, antd, Chakra, Bootstrap, Mantine) e app sem `eslint.config.{mjs,js,cjs}` com `no-restricted-imports` na raiz do app (migração: `frontend-nextjs-lint`) são Médio; `motion` e `framer-motion` instalados juntos são Baixo. Código de teste não conta; `node_modules`, `bin`, `obj`, `.morph`, `.claude` e os contêineres de worktree ficam fora; arquivo ilegível é anotado, nunca derruba. Sem código fonte = GREENFIELD. As contagens são de texto e superestimam consumidores — o relatório aponta `graph affected <símbolo> --uses-only`. Desvio Bloqueante/Alto vira nó da árvore de decisões da `morph-plan`. Com `<feature>`, grava `.morph/features/<f>/2-plan/stack-scan.md` (exit 2 se a feature não existe, 1 fora da árvore dona); sem, imprime o markdown.
|
|
504
508
|
|
|
505
509
|
### tests-audit
|
|
506
510
|
|
|
@@ -33,7 +33,7 @@ typechecks or runs tests: that is `morph-spec verify`.
|
|
|
33
33
|
| **WorktreeRemove** | worktree-remove.js | Fail-open | Routes Claude Code's own cleanup through `morph-spec worktree remove --target --if-clean` (junctions dropped first; a dirty tree is left alone). Always exit 0. Secondary net — the event was observed NOT to fire headless; the Bash guard above stays the primary defense |
|
|
34
34
|
| **PostToolUse** (Write\|Edit) | comment-guard.js | Blocking | Blocks when the edit ADDED a comment the `architecture-code-comments` standard never allows: process history (ADR, scenario/slice id, gate, plan.md), banner/`#region`, a block over 3 lines, or a doc-comment outside a published package. Old comments are never reported; last link of the chain |
|
|
35
35
|
| **PostToolUse** (Write\|Edit\|AskUserQuestion) | state-sync.js | State sync | Autonomous state updates (feature register, output tracking, gate approval) |
|
|
36
|
-
| **PostToolUse** (Write\|Edit\|AskUserQuestion) | gate-guard.js | Advisory | Warns (never blocks) when a gate action disagrees with the persisted gateDecisions
|
|
36
|
+
| **PostToolUse** (Write\|Edit\|AskUserQuestion) | gate-guard.js | Advisory | Warns (never blocks) when a gate action disagrees with the persisted gateDecisions |
|
|
37
37
|
| **PostToolUse** (all tools) | telemetry-log.js | Telemetry | Appends every tool call (skill, sub-agent dispatch + prompt, knowledge-base read, bash/edit) to `.morph/logs/events.jsonl`; silent, fail-open — the NLH execution-path capture read back by `morph-spec telemetry` |
|
|
38
38
|
| **PostToolUse** (all tools) | loop-detect.js | Advisory | Warns when the last 8 actions collapse to ≤2 unique signatures |
|
|
39
39
|
| **PostToolUse** (all tools) | graph-refresh-kick.js | Background | After a Bash or PowerShell command that runs git/gh/morph-spec, launches the DETACHED refresh of the ROOT's codebase graph (`shared/graph-refresh.js`: cwd in the git common dir, never a worktree; one lock per repository; staleness by `shared/graph-freshness.js` — stamp plus the per-tree run record in `<git-common-dir>/morph-spec/graph-refresh-state.json`); never waits, never injects |
|
|
@@ -86,7 +86,7 @@ All Claude Code hooks import from `framework/hooks/shared/`:
|
|
|
86
86
|
|
|
87
87
|
| Module | Purpose |
|
|
88
88
|
|--------|---------|
|
|
89
|
-
| `state-reader.js` | `loadState()`, `getActiveFeature()`, `getFeaturePhase()`, `isGateApproved()`, `getPendingGates()
|
|
89
|
+
| `state-reader.js` | `loadState()`, `getActiveFeature()`, `getFeaturePhase()`, `isGateApproved()`, `getPendingGates()` |
|
|
90
90
|
| `phase-utils.js` | Phase constants, path extraction, file classification |
|
|
91
91
|
| `hook-response.js` | `block(reason)`, `approve(context)`, `injectContext(text)`, `pass()` |
|
|
92
92
|
| `stdin-reader.js` | `readStdin()` — Promise-based stdin JSON reader |
|
|
Binary file
|
|
@@ -15,6 +15,7 @@ const MAX_LISTED = 10;
|
|
|
15
15
|
const RULE_LABEL = {
|
|
16
16
|
process: 'história de processo (ADR, cenário, fatia, gate, plan.md)',
|
|
17
17
|
banner: 'banner ou #region',
|
|
18
|
+
'commented-code': 'código comentado (o git guarda o antigo: apague)',
|
|
18
19
|
'doc-comment': '///·JSDoc em código que não é pacote publicado',
|
|
19
20
|
'long-block': `bloco com mais de ${MAX_BLOCK_LINES} linhas`,
|
|
20
21
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.3.0",
|
|
3
3
|
"patterns": [
|
|
4
4
|
"DROP TABLE",
|
|
5
5
|
"DROP DATABASE",
|
|
@@ -39,6 +39,68 @@
|
|
|
39
39
|
"~/*"
|
|
40
40
|
],
|
|
41
41
|
"reason": "apaga a raiz ou o home inteiro; remova o caminho específico"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"executable": "pkill",
|
|
45
|
+
"args": [
|
|
46
|
+
"node",
|
|
47
|
+
"node.exe"
|
|
48
|
+
],
|
|
49
|
+
"reason": "mata por nome e derruba os MCPs de todas as sessões; encerre pelo PID que você subiu"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"executable": "killall",
|
|
53
|
+
"args": [
|
|
54
|
+
"node",
|
|
55
|
+
"node.exe"
|
|
56
|
+
],
|
|
57
|
+
"reason": "mata por nome e derruba os MCPs de todas as sessões; encerre pelo PID que você subiu"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"executable": "stop-process",
|
|
61
|
+
"args": [
|
|
62
|
+
"-name"
|
|
63
|
+
],
|
|
64
|
+
"reason": "mata por nome e derruba os MCPs de todas as sessões; encerre pelo PID que você subiu (`-Id <n>`)"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"executable": "git",
|
|
68
|
+
"args": [
|
|
69
|
+
"add"
|
|
70
|
+
],
|
|
71
|
+
"flags": [
|
|
72
|
+
[
|
|
73
|
+
"-A",
|
|
74
|
+
"--all"
|
|
75
|
+
]
|
|
76
|
+
],
|
|
77
|
+
"subagentOnly": true,
|
|
78
|
+
"display": "git add -A",
|
|
79
|
+
"reason": "leva junto o arquivo de outra fatia na mesma árvore; `git add <paths>` só dos seus"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"executable": "git",
|
|
83
|
+
"args": [
|
|
84
|
+
"stash"
|
|
85
|
+
],
|
|
86
|
+
"subagentOnly": true,
|
|
87
|
+
"display": "git stash",
|
|
88
|
+
"reason": "mexe na árvore que outras fatias e o verificador estão usando"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"executable": "git",
|
|
92
|
+
"args": [
|
|
93
|
+
"checkout",
|
|
94
|
+
"restore"
|
|
95
|
+
],
|
|
96
|
+
"flags": [
|
|
97
|
+
[
|
|
98
|
+
"--"
|
|
99
|
+
]
|
|
100
|
+
],
|
|
101
|
+
"subagentOnly": true,
|
|
102
|
+
"display": "git checkout --",
|
|
103
|
+
"reason": "reverte código de produção; a prova de que o teste morde é do verificador"
|
|
42
104
|
}
|
|
43
105
|
]
|
|
44
106
|
}
|
|
@@ -18,7 +18,6 @@ const PHASE_DIR_ENTRY_GATE = {
|
|
|
18
18
|
|
|
19
19
|
const ALWAYS_WRITABLE_DIR = '2-plan';
|
|
20
20
|
import { blockResult, runStandalone } from '../../shared/hook-response.js';
|
|
21
|
-
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
22
21
|
import { getFilePath, resolveMorphRoot, resolveTargetPath } from '../../shared/payload-utils.js';
|
|
23
22
|
|
|
24
23
|
export async function run(payload) {
|
|
@@ -57,7 +56,6 @@ export async function run(payload) {
|
|
|
57
56
|
if (targetDir !== allowedDir) {
|
|
58
57
|
const absolutePath = resolveTargetPath(filePath, root);
|
|
59
58
|
if (existsSync(absolutePath)) {
|
|
60
|
-
logHookActivity('enforce-phase-writes', 'PreToolUse', 'ok');
|
|
61
59
|
return null;
|
|
62
60
|
}
|
|
63
61
|
|
|
@@ -69,11 +67,8 @@ export async function run(payload) {
|
|
|
69
67
|
&& existsSync(resolveTargetPath(`.morph/features/${featureName}/1-design`, root));
|
|
70
68
|
|
|
71
69
|
if (!tookDesignBranch || isGateApproved(featureName, 'uiux', root)) {
|
|
72
|
-
logHookActivity('enforce-phase-writes', 'PreToolUse', 'ok');
|
|
73
70
|
return null;
|
|
74
71
|
}
|
|
75
|
-
|
|
76
|
-
logHookActivity('enforce-phase-writes', 'PreToolUse', 'blocked');
|
|
77
72
|
return blockResult(
|
|
78
73
|
`MORPH-SPEC: Writing to '${targetDir}/' is not allowed yet.\n` +
|
|
79
74
|
`This feature entered the uiux branch (1-design/ exists) and the 'uiux' gate is still pending.\n\n` +
|
|
@@ -87,7 +82,6 @@ export async function run(payload) {
|
|
|
87
82
|
const treeNote = root !== process.cwd().replace(/\\/g, '/')
|
|
88
83
|
? `\n(phase read from the tree that owns the file: ${root})`
|
|
89
84
|
: '';
|
|
90
|
-
logHookActivity('enforce-phase-writes', 'PreToolUse', 'blocked');
|
|
91
85
|
return blockResult(
|
|
92
86
|
`MORPH-SPEC: Writing to '${targetDir}/' is not allowed during ${phaseLabel} phase.\n` +
|
|
93
87
|
`Current phase '${phase}' only permits writes to '${allowedDir}/'.${treeNote}\n\n` +
|
|
@@ -96,8 +90,6 @@ export async function run(payload) {
|
|
|
96
90
|
`(run it in that tree — the gate is recorded in its own feature.json).`
|
|
97
91
|
);
|
|
98
92
|
}
|
|
99
|
-
|
|
100
|
-
logHookActivity('enforce-phase-writes', 'PreToolUse', 'ok');
|
|
101
93
|
return null;
|
|
102
94
|
} catch {
|
|
103
95
|
return null;
|