@polymorphism-tech/morph-spec 6.0.0 → 7.0.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/README.md +19 -18
- package/docs/CHEATSHEET.md +3 -3
- package/docs/QUICKSTART.md +4 -4
- package/framework/CLAUDE.md +13 -0
- package/framework/MORPH.md +57 -36
- package/framework/commands/morph-apply.md +4 -21
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-proposal.md +2 -0
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +1 -1
- package/framework/hooks/claude-code/core/protect-spec-files.js +1 -1
- package/framework/hooks/claude-code/core/state-sync.js +212 -0
- package/framework/hooks/shared/phase-utils.js +5 -4
- package/framework/hooks/shared/state-reader.js +1 -1
- package/framework/phases.json +1 -1
- package/framework/rules/morph-workflow.md +15 -34
- package/framework/schemas/tasks.schema.json +35 -0
- package/framework/skills/core/morph-apply/SKILL.md +154 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +53 -30
- package/framework/skills/core/morph-implement/SKILL.md +25 -14
- package/framework/skills/core/morph-plan/SKILL.md +91 -23
- package/framework/skills/core/morph-proposal/SKILL.md +137 -0
- package/framework/skills/core/morph-review/SKILL.md +43 -7
- package/framework/skills/core/morph-stack-scan/SKILL.md +40 -1
- package/framework/standards/STANDARDS.json +4 -5
- package/framework/templates/docs/onboarding.md +4 -4
- package/framework/templates/tasks.template.json +17 -0
- package/package.json +1 -1
- package/src/commands/project/init.js +16 -0
- package/src/commands/validation/validate-feature.js +1 -1
- package/src/core/paths/output-schema.js +6 -6
- package/src/core/state/state-manager.js +1 -1
- package/src/lib/detectors/design-system-detector.js +1 -1
- package/src/lib/installers/memory-installer.js +138 -0
- package/src/lib/phase-chain/phase-validator.js +4 -4
- package/src/lib/risk-detector.js +80 -0
- package/src/lib/tasks/tasks-json-parser.js +185 -0
- package/src/scripts/setup-infra.js +25 -0
- package/src/utils/hooks-installer.js +59 -10
package/README.md
CHANGED
|
@@ -67,11 +67,11 @@ your-project/
|
|
|
67
67
|
│ ├── context/ # Project context (README.md, standards.md)
|
|
68
68
|
│ ├── features/ # Active features
|
|
69
69
|
│ │ └── {feature}/
|
|
70
|
-
│ │ ├── 0-proposal/ #
|
|
71
|
-
│ │ ├── 1-
|
|
72
|
-
│ │ ├── 2-
|
|
73
|
-
│ │ ├── 3-
|
|
74
|
-
│ │ └── 4-
|
|
70
|
+
│ │ ├── 0-proposal/ # Problem statement + acceptance criteria
|
|
71
|
+
│ │ ├── 1-design/ # UI/UX design (optional, UI-heavy features)
|
|
72
|
+
│ │ ├── 2-plan/ # spec.md + mandate.md + tasks.json
|
|
73
|
+
│ │ ├── 3-implement/ # Recap and implementation notes
|
|
74
|
+
│ │ └── 4-review/ # Review report + Gate 3
|
|
75
75
|
│ └── state.json # READ-ONLY — managed by CLI only
|
|
76
76
|
└── .claude/
|
|
77
77
|
├── commands/ # Slash commands
|
|
@@ -88,29 +88,30 @@ your-project/
|
|
|
88
88
|
Features move through structured phases. Some are optional depending on feature type.
|
|
89
89
|
|
|
90
90
|
```
|
|
91
|
-
proposal → [
|
|
91
|
+
proposal → [design] → plan → implement → review
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
`design` is optional — applies only to UI-heavy features.
|
|
95
95
|
|
|
96
96
|
### Phase outputs
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
| Phase | Directory
|
|
100
|
-
| --------- |
|
|
101
|
-
| Proposal | `0-proposal/`
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
| Implement | `5-implement/` | `recap.md` |
|
|
99
|
+
| Phase | Directory | Key files |
|
|
100
|
+
| --------- | --------------- | ------------------------------------------------------------------ |
|
|
101
|
+
| Proposal | `0-proposal/` | `proposal.md`, `stack-scan.md`, `migration-plan.md` (optional) |
|
|
102
|
+
| Design | `1-design/` | `design-system.md`, `mockups.md`, `components.md`, `flows.md` |
|
|
103
|
+
| Plan | `2-plan/` | `spec.md`, `mandate.md`, `tasks.json`, `decisions.md` |
|
|
104
|
+
| Implement | `3-implement/` | `recap.md` + source code |
|
|
105
|
+
| Review | `4-review/` | `review-report.md` |
|
|
107
106
|
|
|
108
107
|
|
|
109
108
|
### Approval gates
|
|
110
109
|
|
|
111
|
-
- **
|
|
112
|
-
- **Plan
|
|
113
|
-
- **
|
|
110
|
+
- **Gate 1 (Proposal)** — Problem statement must be approved before plan.
|
|
111
|
+
- **Gate 2 (Plan)** — Spec + mandate + tasks must be approved before implement.
|
|
112
|
+
- **Gate 3 (Review)** — Review report must be approved before archive.
|
|
113
|
+
|
|
114
|
+
In `trust: "auto"` mode (default), gates auto-pass without risk signals. See `framework/MORPH.md` for risk-signal details.
|
|
114
115
|
|
|
115
116
|
Check gate status:
|
|
116
117
|
|
package/docs/CHEATSHEET.md
CHANGED
|
@@ -25,11 +25,11 @@ Phases in brackets are optional.
|
|
|
25
25
|
| Phase | Directory | Output files | Gate |
|
|
26
26
|
|-------|-----------|--------------|------|
|
|
27
27
|
| proposal | `0-proposal/` | `proposal.md` | ✅ Approval |
|
|
28
|
-
| uiux *(optional)* | `1-
|
|
28
|
+
| uiux *(optional)* | `1-design/` | `design-system.md`, `mockups.md`, `components.md`, `flows.md` | ✅ Approval |
|
|
29
29
|
| design | `2-design/` | `spec.md`, `contracts.cs`, `decisions.md` | ✅ Approval |
|
|
30
30
|
| plan | `3-plan/` | `plan.md` | ✅ Approval |
|
|
31
31
|
| tasks | `4-tasks/` | `tasks.md` | ✅ Approval |
|
|
32
|
-
| implement | `
|
|
32
|
+
| implement | `3-implement/` | code + `recap.md` | — |
|
|
33
33
|
| sync *(optional)* | — | standards updated | — |
|
|
34
34
|
|
|
35
35
|
---
|
|
@@ -154,7 +154,7 @@ morph-spec approval-status {feature} # show gate status
|
|
|
154
154
|
├── features/
|
|
155
155
|
│ └── {feature}/
|
|
156
156
|
│ ├── 0-proposal/proposal.md
|
|
157
|
-
│ ├── 1-
|
|
157
|
+
│ ├── 1-design/design-system.md, mockups.md, components.md, flows.md
|
|
158
158
|
│ ├── 2-design/spec.md, contracts.cs, decisions.md
|
|
159
159
|
│ ├── 3-tasks/tasks.md
|
|
160
160
|
│ └── 4-implement/recap.md
|
package/docs/QUICKSTART.md
CHANGED
|
@@ -57,10 +57,10 @@ Loads project context from `.morph/context/`, confirms stack and active agents.
|
|
|
57
57
|
### Phase 2 — UI/UX (conditional)
|
|
58
58
|
|
|
59
59
|
Only runs if frontend agents are active. Claude generates:
|
|
60
|
-
- `1-
|
|
61
|
-
- `1-
|
|
62
|
-
- `1-
|
|
63
|
-
- `1-
|
|
60
|
+
- `1-design/design-system.md` — colors, typography, spacing
|
|
61
|
+
- `1-design/mockups.md` — ASCII wireframes
|
|
62
|
+
- `1-design/components.md` — component specs
|
|
63
|
+
- `1-design/flows.md` — user flows and edge cases
|
|
64
64
|
|
|
65
65
|
You approve the UI design before design phase begins.
|
|
66
66
|
|
package/framework/CLAUDE.md
CHANGED
|
@@ -52,4 +52,17 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
|
|
|
52
52
|
7 personas de domínio dispatchávels via Agent tool:
|
|
53
53
|
`dotnet-senior`, `ef-modeler`, `maf-expert`, `nextjs-expert`, `ui-designer`, `infra-engineer`, `evaluator`.
|
|
54
54
|
|
|
55
|
+
## Regra de precedência de skills
|
|
56
|
+
|
|
57
|
+
Em projetos morph-spec, prefira as skills `morph-*` sobre as equivalentes `superpowers:*`:
|
|
58
|
+
|
|
59
|
+
| Em vez de | Use |
|
|
60
|
+
|-----------|-----|
|
|
61
|
+
| `superpowers:brainstorming` | `morph-brainstorming` |
|
|
62
|
+
| `superpowers:writing-plans` | `morph-plan` |
|
|
63
|
+
| `superpowers:executing-plans` | `morph-apply` |
|
|
64
|
+
| `superpowers:test-driven-development` | seguir `mandate.md` (TDD está embutido) |
|
|
65
|
+
|
|
66
|
+
As skills `morph-*` aplicam Gates, leem o estado da feature em `.morph/` e produzem outputs com os paths corretos. As `superpowers:*` são genéricas e não conhecem o contexto morph-spec.
|
|
67
|
+
|
|
55
68
|
*MORPH-SPEC v5 by Polymorphism Tech*
|
package/framework/MORPH.md
CHANGED
|
@@ -15,7 +15,7 @@ Entre gates, você opera com autonomia total. O dev valida. Você executa.
|
|
|
15
15
|
| Situação | O que você faz |
|
|
16
16
|
|-----------------------------------------------------------------------|-------------------------------------------------------------------------------|
|
|
17
17
|
| Gate 1 / 2 / 3 aprovado | Avançar para próxima fase imediatamente |
|
|
18
|
-
| Tarefa completada na fase implement | Iniciar a próxima
|
|
18
|
+
| Tarefa completada na fase implement | Iniciar a próxima task do tasks.json sem aguardar instrução |
|
|
19
19
|
| Ambiguidade de escopo coberta pelo mandate | Resolver conservadoramente e registrar em decisions.md |
|
|
20
20
|
| Ambiguidade bloqueante (requisito ausente ou contraditório) | UMA pergunta via AskUserQuestion |
|
|
21
21
|
| Sub-agent com auto-score ≥ 7 | Integrar ao recap.md e continuar |
|
|
@@ -76,14 +76,14 @@ Os papéis não são exclusivos — numa sessão longa você transita entre Orch
|
|
|
76
76
|
|
|
77
77
|
O fluxo padrão de uma feature tem quatro fases. Cada fase tem uma pasta dedicada em `.morph/features/{feature}/`.
|
|
78
78
|
|
|
79
|
-
| Fase | Pasta | Gate
|
|
80
|
-
|
|
81
|
-
| proposal | `0-proposal/` |
|
|
82
|
-
| plan | `2-plan/` |
|
|
83
|
-
| implement | `3-implement/` | Nenhum
|
|
84
|
-
| review | `4-review/` |
|
|
79
|
+
| Fase | Pasta | Gate | O que termina a fase |
|
|
80
|
+
|-------------|----------------|-----------------------|-------------------------------------------------|
|
|
81
|
+
| proposal | `0-proposal/` | Gate 1 (trust-aware) | `proposal.md` aprovado e commitado |
|
|
82
|
+
| plan | `2-plan/` | Gate 2 (trust-aware) | `spec.md`, `mandate.md`, `tasks.json` aprovados |
|
|
83
|
+
| implement | `3-implement/` | Nenhum | Todas as tasks do tasks.json com status `done` |
|
|
84
|
+
| review | `4-review/` | Gate 3 (trust-aware) | `review-report.md` aprovado e commitado |
|
|
85
85
|
|
|
86
|
-
A fase `1-
|
|
86
|
+
A fase `1-design/` (design system, mockups, flows) é opcional — aplica-se apenas a features com componentes de UI novos e deve ser explicitamente requisitada pelo usuário.
|
|
87
87
|
|
|
88
88
|
Não avance de fase sem o gate correspondente ter sido confirmado.
|
|
89
89
|
|
|
@@ -104,7 +104,7 @@ Não invente caminhos alternativos.
|
|
|
104
104
|
|
|
105
105
|
- `mandate.md` — contrato de implementação: stack exata, standards obrigatórios (com links), patterns explicitamente proibidos, checklist de done com critérios verificáveis. O mandate.md é lido no início de cada tarefa de implementação — deve ser específico o suficiente para resolver ambiguidades sem consultar o Orchestrator.
|
|
106
106
|
|
|
107
|
-
- `tasks.
|
|
107
|
+
- `tasks.json` — task list estruturada validada contra `framework/schemas/tasks.schema.json`. Cada task tem `id` (T1, T2...), `title`, `description`, `dependencies` (array de IDs), `effort` (S/M/L/XL), `doneCriteria`, `status` (pending/in_progress/done/skipped/blocked) e `outputs` (paths esperados). Cada entrada no recap.md deve referenciar um ID do tasks.json.
|
|
108
108
|
|
|
109
109
|
- `decisions.md` — log de decisões técnicas: qual era a alternativa, por que foi descartada, quem decidiu. Criado quando surgir a primeira decisão não trivial. Não é obrigatório — só existe se houver decisões que precisam de rastreabilidade.
|
|
110
110
|
|
|
@@ -148,29 +148,42 @@ O agente responsável por um domínio está listado em `framework/agents.json`.
|
|
|
148
148
|
|
|
149
149
|
---
|
|
150
150
|
|
|
151
|
-
## 5. Gates de Aprovação
|
|
151
|
+
## 5. Gates de Aprovação (Trust Mode)
|
|
152
152
|
|
|
153
|
-
Gates são pontos
|
|
153
|
+
Gates são pontos de **pausa potencial**. Em `trust: "auto"` (default), gates **auto-passam** se nenhum sinal de risco for detectado — apenas exibem um summary curto. Em `trust: "manual"` OU se um sinal de risco fizer fire, o gate pausa via `AskUserQuestion` com options padrão:
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
```
|
|
156
|
+
- "aprovado, prosseguir"
|
|
157
|
+
- "preciso ajustar"
|
|
158
|
+
- "cancelar feature" (Gate 3 usa "rollback" em vez de "cancelar")
|
|
159
|
+
```
|
|
160
160
|
|
|
161
|
-
**
|
|
162
|
-
Condição: o usuário leu e aprovou `spec.md`, `mandate.md` e `tasks.md`.
|
|
163
|
-
Como ativar: use o template de apresentação do Gate 2 em `framework/skills/core/morph-plan/SKILL.md` passo 5. Apresente o gate completo — spec em uma frase, mandate resumido, tasks com estimativa, decisões de arquitetura se houver.
|
|
164
|
-
Se o usuário pedir ajustes, aplique e reapresente o gate completo antes de avançar.
|
|
165
|
-
Sinal de gate aberto: aprovação explícita de todos os três artefatos, ou "pode implementar".
|
|
166
|
-
Atenção: aprovação parcial ("o spec está bom") não abre o gate — todos os três precisam ser confirmados.
|
|
161
|
+
**Sinais de risco que forçam pausa em auto** (detectados via `src/lib/risk-detector.js`):
|
|
167
162
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
| Sinal | Threshold | Quando aplica |
|
|
164
|
+
|-------|-----------|---------------|
|
|
165
|
+
| `designSystemDivergence` | true | Brainstorming detecta fontes conflitantes (spec antigo vs bundle local) |
|
|
166
|
+
| `standardsViolations` | array.length > 0 | Mandate ou implementation viola padrões proibidos |
|
|
167
|
+
| `evalScore` | < 8 | morph-eval/morph-review produz score abaixo do mínimo |
|
|
168
|
+
| `taskCount` | > 20 | Escopo grande — provavelmente vale revisar antes de implementar |
|
|
172
169
|
|
|
173
|
-
**
|
|
170
|
+
**Gate 1 — Proposta**
|
|
171
|
+
- Sinais: `designSystemDivergence`, `standardsViolations` (improvável).
|
|
172
|
+
- Skill responsável: `morph-brainstorming`.
|
|
173
|
+
|
|
174
|
+
**Gate 2 — Plano**
|
|
175
|
+
- Sinais: todos do Gate 1 + `taskCount`, `standardsViolations` em mandate.
|
|
176
|
+
- Skill responsável: `morph-plan`.
|
|
177
|
+
|
|
178
|
+
**Gate 3 — Review**
|
|
179
|
+
- Sinais: `evalScore` composite, `standardsViolations` detectadas em implementation.
|
|
180
|
+
- Skill responsável: `morph-review`.
|
|
181
|
+
|
|
182
|
+
Para alterar o modo: editar `.morph/config/config.json → trust` (`"auto"` ou `"manual"`). Configs sem o campo `trust` defaultam para `"auto"`.
|
|
183
|
+
|
|
184
|
+
**Comportamento padrão fora de gate:** trabalhe com autonomia total. Gates só existem nestes três pontos. Não pause para confirmação no meio de implementação a menos que encontre uma ambiguidade de escopo que o mandate não cobre — nesse caso, registre a ambiguidade no decisions.md, resolva da forma mais conservadora possível, e sinalize na entrega da task.
|
|
185
|
+
|
|
186
|
+
**O hook state-sync.js** detecta as respostas dos Gates automaticamente: quando uma `AskUserQuestion` é respondida com label começando em "aprovado", marca o gate correspondente em `.morph/state.json`. Em modo auto sem AskUserQuestion, a skill atualiza state.json diretamente (allowlist permite Edit em `.morph/state.json`).
|
|
174
187
|
|
|
175
188
|
---
|
|
176
189
|
|
|
@@ -219,15 +232,23 @@ O padrão de dispatch e o template de prompt para sub-agents estão detalhados e
|
|
|
219
232
|
Skills são documentos de instrução que você invoca sob demanda. Eles ficam em `framework/skills/core/`.
|
|
220
233
|
Não invoque uma skill fora do momento indicado.
|
|
221
234
|
|
|
222
|
-
| Skill | Path | Quando invocar
|
|
223
|
-
|
|
224
|
-
| morph-
|
|
225
|
-
| morph-
|
|
226
|
-
| morph-
|
|
227
|
-
| morph-
|
|
228
|
-
| morph-
|
|
229
|
-
| morph-
|
|
230
|
-
| morph-
|
|
235
|
+
| Skill | Path | Quando invocar |
|
|
236
|
+
|-----------------------|----------------------------------------------------|-------------------------------------------------------------------------|
|
|
237
|
+
| **morph-proposal** | `framework/skills/core/morph-proposal/SKILL.md` | **Orquestrador**: brainstorming → Gate 1 → plan → Gate 2 (entry point) |
|
|
238
|
+
| **morph-apply** | `framework/skills/core/morph-apply/SKILL.md` | **Orquestrador**: DAG-adaptive implement loop → review → Gate 3 |
|
|
239
|
+
| morph-brainstorming | `framework/skills/core/morph-brainstorming/SKILL.md` | Invocada pela morph-proposal — entendimento de negócio + stack-scan |
|
|
240
|
+
| morph-stack-scan | `framework/skills/core/morph-stack-scan/SKILL.md` | Detecta stack atual; curto-circuita em greenfield |
|
|
241
|
+
| morph-plan | `framework/skills/core/morph-plan/SKILL.md` | Invocada pela morph-proposal — produz spec/mandate/tasks.json |
|
|
242
|
+
| morph-implement | `framework/skills/core/morph-implement/SKILL.md` | Invocada pela morph-apply — executa cada task |
|
|
243
|
+
| morph-eval | `framework/skills/core/morph-eval/SKILL.md` | Após concluir uma task ou fase |
|
|
244
|
+
| morph-review | `framework/skills/core/morph-review/SKILL.md` | Invocada pela morph-apply — produz review-report + Gate 3 |
|
|
245
|
+
| morph-standards | `framework/skills/core/morph-standards/SKILL.md` | Quando precisar localizar ou aplicar um standard específico |
|
|
246
|
+
|
|
247
|
+
**Entry points** para o usuário:
|
|
248
|
+
- `/morph-proposal {feature}` → invoca `Skill(morph-proposal)`, que orquestra brainstorming + plan
|
|
249
|
+
- `/morph-apply {feature}` → invoca `Skill(morph-apply)`, que orquestra implement + review
|
|
250
|
+
|
|
251
|
+
Os slash commands são atalhos: o agente pode chamar `Skill(morph-proposal)` diretamente quando o usuário pede "use /morph-proposal".
|
|
231
252
|
|
|
232
253
|
Cada skill contém instruções de execução passo a passo. Leia, siga, não desvie sem justificativa registrada no recap.md.
|
|
233
254
|
|
|
@@ -6,7 +6,9 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, Agent, AskUserQuestion, Skil
|
|
|
6
6
|
|
|
7
7
|
# /morph-apply — NLH Implementation
|
|
8
8
|
|
|
9
|
-
Executa as
|
|
9
|
+
Executa as tasks de uma feature até o Gate 3.
|
|
10
|
+
|
|
11
|
+
**Implementação:** este slash command é um atalho. O agente deve invocar `Skill(morph-apply)` com o nome da feature. A skill decide adaptativamente entre execução sequencial (auto-advance entre tasks) ou paralela (Agent dispatch para tasks independentes) baseado no DAG do `tasks.json`. Termina com `morph-review` + Gate 3 trust-aware.
|
|
10
12
|
|
|
11
13
|
## Argumentos
|
|
12
14
|
|
|
@@ -17,29 +19,10 @@ Executa as tarefas do `tasks.md` em sequência sem pausa, depois apresenta o Gat
|
|
|
17
19
|
Antes de começar, verifique:
|
|
18
20
|
- `.morph/features/{feature-name}/2-plan/spec.md` existe
|
|
19
21
|
- `.morph/features/{feature-name}/2-plan/mandate.md` existe
|
|
20
|
-
- `.morph/features/{feature-name}/2-plan/tasks.
|
|
21
|
-
- `.morph/features/{feature-name}/3-implement/recap.md` existe ou pode ser criado
|
|
22
|
+
- `.morph/features/{feature-name}/2-plan/tasks.json` existe e é válido contra o schema
|
|
22
23
|
|
|
23
24
|
Se algum estiver faltando, abortar e pedir ao usuário para rodar `/morph-proposal {feature-name}` primeiro.
|
|
24
25
|
|
|
25
|
-
## Fluxo
|
|
26
|
-
|
|
27
|
-
1. **Loop de tarefas** — para cada tarefa pendente em `tasks.md`:
|
|
28
|
-
- Invocar a skill `morph-implement` em `framework/skills/core/morph-implement/`.
|
|
29
|
-
- A skill executa a tarefa, registra em `recap.md`, atualiza o trace.
|
|
30
|
-
- **Auto-advance:** ao concluir uma tarefa, passar imediatamente para a próxima sem aguardar instrução do usuário.
|
|
31
|
-
- Pausar apenas se encontrar ambiguidade bloqueante não coberta pelo `mandate.md`.
|
|
32
|
-
|
|
33
|
-
2. **Após todas as tarefas concluídas, invocar `morph-review`** (skill em `framework/skills/core/morph-review/`):
|
|
34
|
-
- Lê `mandate.md` e `recap.md`.
|
|
35
|
-
- Aplica `morph-eval` para gerar scores por dimensão.
|
|
36
|
-
- Produz `review-report.md` em `.morph/features/{feature-name}/4-review/`.
|
|
37
|
-
- Apresenta o **Gate 3**.
|
|
38
|
-
|
|
39
|
-
3. **Aguardar aprovação do Gate 3.**
|
|
40
|
-
- Resposta válida: "aprovado", "merge".
|
|
41
|
-
- Se houver itens bloqueantes (score < 6): corrija antes de apresentar o gate.
|
|
42
|
-
|
|
43
26
|
## Anti-padrões
|
|
44
27
|
|
|
45
28
|
- Não pausar entre tarefas para confirmação — o tasks.md já foi aprovado no Gate 2.
|
|
@@ -30,7 +30,7 @@ If any precondition fails, stop and report what is missing instead of archiving.
|
|
|
30
30
|
.morph/features/{feature}/ → .morph/archive/{feature}/
|
|
31
31
|
```
|
|
32
32
|
Use `Bash` (`mv` on POSIX, `Move-Item` on Windows). Preserve all phase subfolders
|
|
33
|
-
(`0-proposal/`, `1-
|
|
33
|
+
(`0-proposal/`, `1-design/`, `2-plan/`, `3-implement/`, `4-review/`).
|
|
34
34
|
|
|
35
35
|
3. **Update state.json**:
|
|
36
36
|
- Set `features[name].status = 'completed'`
|
|
@@ -8,6 +8,8 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, Agent, AskUserQuestion, Skil
|
|
|
8
8
|
|
|
9
9
|
Conduz a feature do request inicial até o Gate 2 (plano aprovado), sem intervenção do usuário entre os gates.
|
|
10
10
|
|
|
11
|
+
**Implementação:** este slash command é um atalho. O agente deve invocar `Skill(morph-proposal)` com o nome da feature como argumento. A skill encadeia automaticamente `morph-brainstorming` → Gate 1 → `morph-plan` → Gate 2. Em `trust: "auto"` (default), gates auto-passam sem sinais de risco.
|
|
12
|
+
|
|
11
13
|
## Argumentos
|
|
12
14
|
|
|
13
15
|
`{feature-name}` — nome em kebab-case da feature.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Ensures writes to .morph/features/{feature}/ go to the current phase's subfolder:
|
|
9
9
|
* proposal → only 0-proposal/
|
|
10
|
-
* uiux → only 1-
|
|
10
|
+
* uiux → only 1-design/
|
|
11
11
|
* plan → only 2-plan/
|
|
12
12
|
* implement → 3-implement/ + any source code (unrestricted)
|
|
13
13
|
* review → only 4-review/
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Blocks Write/Edit to spec artifacts after their approval gate has passed:
|
|
9
9
|
* - 2-plan/spec.md, contracts.cs, tasks.md, etc. → blocked if 'plan' gate approved
|
|
10
|
-
* - 1-
|
|
10
|
+
* - 1-design/design-system.md, etc. → blocked if 'uiux' gate approved
|
|
11
11
|
*
|
|
12
12
|
* Fail-open: exits 0 on any error.
|
|
13
13
|
*/
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* state-sync.js — PostToolUse hook
|
|
4
|
+
*
|
|
5
|
+
* Keeps .morph/state.json in sync with the agent's actions, without requiring
|
|
6
|
+
* the agent to think about state. Fail-open: any error returns {} silently.
|
|
7
|
+
*
|
|
8
|
+
* Triggers:
|
|
9
|
+
* - Write/Edit of .morph/features/{feature}/0-proposal/proposal.md → register feature
|
|
10
|
+
* - Write/Edit of .morph/features/{feature}/{phase}/{output} → mark output created
|
|
11
|
+
* - AskUserQuestion answered with option starting "aprovado" → advance gate
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, existsSync, renameSync, writeFileSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { loadState, stateExists } from '../../shared/state-reader.js';
|
|
17
|
+
|
|
18
|
+
const STATE_FILE = '.morph/state.json';
|
|
19
|
+
|
|
20
|
+
const PHASE_FOLDERS = {
|
|
21
|
+
'0-proposal': 'proposal',
|
|
22
|
+
'1-design': 'design',
|
|
23
|
+
'1-ui': 'uiux', // legacy alias — still recognized during folder transition
|
|
24
|
+
'2-plan': 'plan',
|
|
25
|
+
'3-implement': 'implement',
|
|
26
|
+
'4-review': 'review',
|
|
27
|
+
'5-implement': 'implement', // legacy alias
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function exit(output = {}) {
|
|
31
|
+
process.stdout.write(JSON.stringify(output));
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readStdin() {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
38
|
+
} catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function projectDir() {
|
|
44
|
+
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function extractFeatureName(filePath) {
|
|
48
|
+
if (!filePath) return null;
|
|
49
|
+
const normalized = String(filePath).replace(/\\/g, '/');
|
|
50
|
+
const match = normalized.match(/\.morph\/features\/([^/]+)\//);
|
|
51
|
+
return match ? match[1] : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function extractPhaseFolder(filePath) {
|
|
55
|
+
if (!filePath) return null;
|
|
56
|
+
const normalized = String(filePath).replace(/\\/g, '/');
|
|
57
|
+
const match = normalized.match(/\.morph\/features\/[^/]+\/([^/]+)\//);
|
|
58
|
+
if (!match) return null;
|
|
59
|
+
return PHASE_FOLDERS[match[1]] ?? null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function extractFilename(filePath) {
|
|
63
|
+
const normalized = String(filePath).replace(/\\/g, '/');
|
|
64
|
+
return normalized.split('/').pop();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Atomic write — temp file then rename. Mirrors state-manager.js semantics.
|
|
69
|
+
*/
|
|
70
|
+
function writeStateAtomic(cwd, state) {
|
|
71
|
+
const path = join(cwd, STATE_FILE);
|
|
72
|
+
const tmpPath = `${path}.tmp.${process.pid}`;
|
|
73
|
+
writeFileSync(tmpPath, JSON.stringify(state, null, 2), 'utf8');
|
|
74
|
+
renameSync(tmpPath, path);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function registerFeatureIfMissing(state, featureName) {
|
|
78
|
+
if (!state.features) state.features = {};
|
|
79
|
+
if (state.features[featureName]) return false;
|
|
80
|
+
|
|
81
|
+
const now = new Date().toISOString();
|
|
82
|
+
state.features[featureName] = {
|
|
83
|
+
status: 'in_progress',
|
|
84
|
+
createdAt: now,
|
|
85
|
+
updatedAt: now,
|
|
86
|
+
approvalGates: { proposal: false, plan: false, review: false },
|
|
87
|
+
outputs: {},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (!state.metadata) state.metadata = {};
|
|
91
|
+
state.metadata.totalFeatures = Object.keys(state.features).length;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function trackOutput(state, featureName, filePath) {
|
|
96
|
+
const filename = extractFilename(filePath);
|
|
97
|
+
const feature = state.features[featureName];
|
|
98
|
+
if (!feature) return false;
|
|
99
|
+
feature.outputs ??= {};
|
|
100
|
+
const normalizedPath = String(filePath).replace(/\\/g, '/');
|
|
101
|
+
const wasMissing = !feature.outputs[filename]?.created;
|
|
102
|
+
feature.outputs[filename] = {
|
|
103
|
+
created: true,
|
|
104
|
+
path: normalizedPath,
|
|
105
|
+
};
|
|
106
|
+
feature.updatedAt = new Date().toISOString();
|
|
107
|
+
return wasMissing;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function findActiveFeature(state) {
|
|
111
|
+
if (!state.features) return null;
|
|
112
|
+
const entries = Object.entries(state.features);
|
|
113
|
+
const active = entries.find(([, f]) => f.status === 'in_progress');
|
|
114
|
+
if (active) return active[0];
|
|
115
|
+
return entries[0]?.[0] ?? null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isApprovalAnswer(answer) {
|
|
119
|
+
if (typeof answer !== 'string') return false;
|
|
120
|
+
const lower = answer.toLowerCase();
|
|
121
|
+
return (
|
|
122
|
+
lower.startsWith('aprovado') ||
|
|
123
|
+
lower.includes('pode avan') ||
|
|
124
|
+
lower.includes('pode implementar') ||
|
|
125
|
+
lower.includes('pode prosseguir')
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function gateFromQuestion(question) {
|
|
130
|
+
if (typeof question !== 'string') return null;
|
|
131
|
+
const lower = question.toLowerCase();
|
|
132
|
+
if (lower.includes('gate 1') || lower.includes('proposta')) return 'proposal';
|
|
133
|
+
if (lower.includes('gate 2') || lower.includes('plano') || lower.includes('plan')) return 'plan';
|
|
134
|
+
if (lower.includes('gate 3') || lower.includes('review')) return 'review';
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handleWriteEdit(input, cwd) {
|
|
139
|
+
const filePath = input.tool_input?.file_path;
|
|
140
|
+
const featureName = extractFeatureName(filePath);
|
|
141
|
+
if (!featureName) return false;
|
|
142
|
+
|
|
143
|
+
const state = loadState(cwd);
|
|
144
|
+
if (!state) return false;
|
|
145
|
+
|
|
146
|
+
let changed = registerFeatureIfMissing(state, featureName);
|
|
147
|
+
const phase = extractPhaseFolder(filePath);
|
|
148
|
+
if (phase) {
|
|
149
|
+
const outputChanged = trackOutput(state, featureName, filePath);
|
|
150
|
+
changed = changed || outputChanged;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (changed) {
|
|
154
|
+
writeStateAtomic(cwd, state);
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function handleAskUserQuestion(input, cwd) {
|
|
160
|
+
const answers = input.tool_response?.answers ?? input.tool_response?.responses ?? {};
|
|
161
|
+
if (typeof answers !== 'object' || answers === null) return false;
|
|
162
|
+
|
|
163
|
+
const state = loadState(cwd);
|
|
164
|
+
if (!state) return false;
|
|
165
|
+
|
|
166
|
+
const featureName = findActiveFeature(state);
|
|
167
|
+
if (!featureName) return false;
|
|
168
|
+
|
|
169
|
+
let changed = false;
|
|
170
|
+
for (const [question, answer] of Object.entries(answers)) {
|
|
171
|
+
if (!isApprovalAnswer(answer)) continue;
|
|
172
|
+
const gate = gateFromQuestion(question);
|
|
173
|
+
if (!gate) continue;
|
|
174
|
+
|
|
175
|
+
state.features[featureName].approvalGates ??= {};
|
|
176
|
+
if (state.features[featureName].approvalGates[gate] !== true) {
|
|
177
|
+
state.features[featureName].approvalGates[gate] = true;
|
|
178
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
179
|
+
changed = true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (changed) {
|
|
184
|
+
writeStateAtomic(cwd, state);
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function main() {
|
|
190
|
+
const cwd = projectDir();
|
|
191
|
+
if (!stateExists(cwd)) {
|
|
192
|
+
exit();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const input = readStdin();
|
|
197
|
+
const toolName = input.tool_name;
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
if (toolName === 'Write' || toolName === 'Edit') {
|
|
201
|
+
handleWriteEdit(input, cwd);
|
|
202
|
+
} else if (toolName === 'AskUserQuestion') {
|
|
203
|
+
handleAskUserQuestion(input, cwd);
|
|
204
|
+
}
|
|
205
|
+
} catch {
|
|
206
|
+
// fail-open
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
exit();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
main().catch(() => exit());
|
|
@@ -8,15 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/** Phase order */
|
|
11
|
-
export const PHASE_ORDER = ["proposal","uiux","plan","implement","review"];
|
|
11
|
+
export const PHASE_ORDER = ["proposal","uiux","plan","implement","review","sync"];
|
|
12
12
|
|
|
13
13
|
/** Map phase → allowed output subdirectory */
|
|
14
14
|
export const PHASE_DIRS = {
|
|
15
15
|
proposal: '0-proposal',
|
|
16
|
-
uiux: '1-
|
|
16
|
+
uiux: '1-design',
|
|
17
17
|
plan: '2-plan',
|
|
18
18
|
implement: '3-implement',
|
|
19
19
|
review: '4-review',
|
|
20
|
+
sync: '3-implement',
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
/** Map output type (camelCase) → phase that produces it */
|
|
@@ -47,7 +48,7 @@ export const FILENAME_TO_OUTPUT = {
|
|
|
47
48
|
'flows.md': 'uiFlows',
|
|
48
49
|
'spec.md': 'spec',
|
|
49
50
|
'mandate.md': 'mandate',
|
|
50
|
-
'tasks.
|
|
51
|
+
'tasks.json': 'tasks',
|
|
51
52
|
'decisions.md': 'decisions',
|
|
52
53
|
'recap.md': 'recap',
|
|
53
54
|
'review-report.md': 'reviewReport',
|
|
@@ -61,7 +62,7 @@ export const PROTECTED_SPEC_FILES = {
|
|
|
61
62
|
'flows.md': 'uiux',
|
|
62
63
|
'spec.md': 'plan',
|
|
63
64
|
'mandate.md': 'plan',
|
|
64
|
-
'tasks.
|
|
65
|
+
'tasks.json': 'plan',
|
|
65
66
|
};
|
|
66
67
|
|
|
67
68
|
/**
|
|
@@ -110,7 +110,7 @@ export function derivePhaseForFeature(featureName, projectPath) {
|
|
|
110
110
|
['4-review', 'review'],
|
|
111
111
|
['3-implement', 'implement'],
|
|
112
112
|
['2-plan', 'plan'],
|
|
113
|
-
['1-
|
|
113
|
+
['1-design', 'uiux'],
|
|
114
114
|
['0-proposal', 'proposal'],
|
|
115
115
|
];
|
|
116
116
|
for (const [folder, phase] of phaseMap) {
|