@polymorphism-tech/morph-spec 8.3.0 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +5 -5
  2. package/bin/morph-spec.js +24 -1
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +6 -4
  5. package/docs/QUICKSTART.md +13 -7
  6. package/framework/CLAUDE.md +5 -3
  7. package/framework/MORPH.md +2 -2
  8. package/framework/commands/morph-hotfix.md +1 -1
  9. package/framework/hooks/claude-code/core/state-sync.js +41 -9
  10. package/framework/hooks/shared/state-writer.js +14 -3
  11. package/framework/schemas/feature.schema.json +15 -2
  12. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  13. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  14. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  15. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  16. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  17. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  18. package/framework/skills/core/morph-implement/SKILL.md +1 -1
  19. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  20. package/framework/skills/core/morph-proposal/SKILL.md +4 -4
  21. package/framework/skills/core/morph-review/SKILL.md +1 -1
  22. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  23. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  24. package/package.json +1 -1
  25. package/src/commands/project/doctor.js +28 -0
  26. package/src/commands/state/advance.js +17 -0
  27. package/src/commands/state/approve.js +13 -0
  28. package/src/commands/state/create.js +21 -1
  29. package/src/commands/state/delete.js +143 -0
  30. package/src/commands/state/list.js +110 -0
  31. package/src/core/state/feature-identity.js +36 -0
  32. package/src/core/state/gate-order.js +51 -0
  33. package/src/core/state/lifecycle-audit.js +79 -0
  34. package/src/core/state/state-manager.js +47 -12
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
4
4
 
5
5
  **Package:** `@polymorphism-tech/morph-spec`
6
- **Version:** 8.3.0
6
+ **Version:** 8.4.0
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -184,8 +184,8 @@ morph-spec verify {feature} [task] # Deterministic build+tests+validators ch
184
184
  ### Work-type routing
185
185
 
186
186
  ```bash
187
- morph-spec create {feature} --type feature|bug|chore|hotfix # Explicit birth: type dimensions the pipeline
188
- morph-spec create {feature} --request "<free text>" # Deterministic detector classifies the type
187
+ morph-spec create {feature} --type feature|bug|chore|hotfix --description "<what/why>" # Explicit birth: type dimensions the pipeline, description is required
188
+ morph-spec create {feature} --request "<free text>" # Detector classifies the type AND seeds the description
189
189
  morph-spec retype {feature} {newType} # Change work type mid-flight (blocked after Gate 3)
190
190
  morph-spec dag {feature} [--mermaid] # DAG dispatch decision; --mermaid emits a flowchart
191
191
  ```
@@ -193,7 +193,7 @@ morph-spec dag {feature} [--mermaid] # DAG dispatch d
193
193
  ### Worktrees (parallel development)
194
194
 
195
195
  ```bash
196
- morph-spec create {feature} --worktree # Birth the feature already isolated in its own worktree
196
+ morph-spec create {feature} --description "<what/why>" --worktree # Birth the feature already isolated in its own worktree
197
197
  morph-spec worktree setup {feature} # Create .worktrees/{feature}/ on branch morph/{feature}
198
198
  morph-spec worktree link [--all] # Relink shared infra (node_modules, .claude, .morph/framework)
199
199
  morph-spec worktree finish {feature} # Merge --no-ff back to the primary root, clean up (requires Gate 3)
@@ -365,4 +365,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
365
365
 
366
366
  ---
367
367
 
368
- *morph-spec v8.3.0 by [Polymorphism Tech](https://polymorphism.tech)*
368
+ *morph-spec v8.4.0 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -35,6 +35,8 @@ import { gateDecisionCommand } from '../src/commands/state/gate-decision.js';
35
35
  import { createCommand } from '../src/commands/state/create.js';
36
36
  import { retypeCommand } from '../src/commands/state/retype.js';
37
37
  import { handoffCommand } from '../src/commands/state/handoff.js';
38
+ import { listCommand } from '../src/commands/state/list.js';
39
+ import { deleteCommand } from '../src/commands/state/delete.js';
38
40
 
39
41
  // === Tasks ===
40
42
  import { dagCommand } from '../src/commands/tasks/dag.js';
@@ -126,13 +128,22 @@ program
126
128
  .option('-v, --verbose', 'Show detailed output')
127
129
  .action(statusCommand);
128
130
 
131
+ program
132
+ .command('list')
133
+ .description('List every feature × status × gates (G1/G2/G3) × workType — the audit board')
134
+ .option('--all', 'Include archived features (.morph/archive)')
135
+ .option('--archived', 'Include archived features (alias of --all)')
136
+ .option('--json', 'Output as JSON')
137
+ .action((options) => listCommand(options));
138
+
129
139
  // ─── Approval gates (dev-only) ────────────────────────────────────────────
130
140
  program
131
141
  .command('approve <feature> <gate>')
132
- .description('Approve a phase gate (proposal, plan, review)')
142
+ .description('Approve a phase gate (proposal, plan, review). Enforces gate order (N requires N-1)')
133
143
  .option('--approver <name>', 'Name of approver (default: current user when interactive, "auto" otherwise)')
134
144
  .option('--mode <mode>', 'Approval mode: "user" or "auto" (default: detected from TTY)')
135
145
  .option('--notes <notes>', 'Approval notes')
146
+ .option('--force', 'Approve out of order (bypass the gate-monotonicity check)')
136
147
  .action((feature, gate, options) => approveCommand(feature, gate, options));
137
148
 
138
149
  program
@@ -142,6 +153,7 @@ program
142
153
  .option('--mode <mode>', 'Approval mode: "user" or "auto" (default: detected from TTY)')
143
154
  .option('--design', 'From proposal, advance into the optional design (uiux) phase instead of plan')
144
155
  .option('--notes <notes>', 'Approval notes')
156
+ .option('--force', 'Advance even if a prerequisite gate is unapproved (out-of-order recovery)')
145
157
  .action((feature, options) => advanceCommand(feature, options));
146
158
 
147
159
  program
@@ -151,6 +163,15 @@ program
151
163
  .option('--force', 'Archive even if Gate 3 (review) is not approved')
152
164
  .action((feature, options) => archiveCommand(feature, options));
153
165
 
166
+ program
167
+ .command('delete <feature>')
168
+ .description('Delete a feature (hard by default; --archive marks it abandoned). Refuses if a gate is approved unless --force')
169
+ .option('--archive', 'Soft delete: move to .morph/archive with status "abandoned" instead of removing')
170
+ .option('--force', 'Delete even when a gate is approved')
171
+ .option('--no-git', 'Skip git staging of the removal')
172
+ .option('--json', 'Output as JSON')
173
+ .action((feature, options) => deleteCommand(feature, options));
174
+
154
175
  program
155
176
  .command('approval-status <feature>')
156
177
  .description('Show approval status for all gates')
@@ -178,6 +199,8 @@ program
178
199
  program
179
200
  .command('create <feature>')
180
201
  .description('Classify work type (--type/--request), scaffold + stamp policy gates, and create the canonical feature.json (explicit birth — see framework/schemas/feature.schema.json)')
202
+ .option('--name <name>', 'Human-readable feature name (default: the slug)')
203
+ .option('--description <text>', 'What/why of the feature — required at birth unless --request is given (it seeds the description)')
181
204
  .option('--status <status>', 'Initial feature status (default: in_progress)')
182
205
  .option('--type <type>', 'Work type: feature|bug|chore|hotfix (default: feature) — see src/core/work-types.js')
183
206
  .option('--request <text>', 'Free-text request classified by the deterministic detector; tie/none exits 2 with candidates for an LLM/user decision (feature NOT created)')
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "8.3.0",
3
+ "version": "8.4.0",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",
@@ -68,9 +68,11 @@ morph-spec archive {feature} [--force] [--no-git]
68
68
 
69
69
  Every feature is born with a **work type** that dimensions its pipeline: `feature` (full proposal → [uiux] → plan → implement → review), `bug` (lean), `chore` (fast-track straight to implement), `hotfix` (scout → surgical fix → human Gate 3 always, even under `trust: auto`).
70
70
 
71
+ Every feature is also born with an **identity**: `--description` is required (`create` refuses a nameless scaffold without it), `name` defaults to the slug.
72
+
71
73
  ```bash
72
- morph-spec create {feature} --type feature|bug|chore|hotfix
73
- morph-spec create {feature} --request "<free text>" # deterministic detector classifies the type
74
+ morph-spec create {feature} --type feature|bug|chore|hotfix --description "<what/why>"
75
+ morph-spec create {feature} --request "<free text>" # detector classifies the type AND seeds the description
74
76
  morph-spec retype {feature} {newType} # change type mid-flight (blocked after Gate 3)
75
77
  ```
76
78
 
@@ -111,7 +113,7 @@ morph-spec dag {feature} --mermaid # emit the tasks.json DAG as a Mermai
111
113
  One feature = one git worktree = one Claude Code instance. Each worktree gets its own deterministic port block (`.morph/worktree.env`) and a regenerable handoff document.
112
114
 
113
115
  ```bash
114
- morph-spec create {feature} --worktree # birth the feature already isolated in its own worktree
116
+ morph-spec create {feature} --description "<what/why>" --worktree # birth the feature already isolated in its own worktree
115
117
  morph-spec worktree setup {feature} # create .worktrees/{feature}/ on branch morph/{feature}
116
118
  morph-spec worktree link [--all] # relink shared infra (node_modules[, .claude, .morph/framework])
117
119
  morph-spec worktree finish {feature} # merge --no-ff back to the primary root, clean up + release ports (requires Gate 3)
@@ -180,4 +182,4 @@ Never edited directly — go through CLI commands or `morph-spec update` instead
180
182
 
181
183
  ---
182
184
 
183
- *morph-spec v8.3.0 by Polymorphism Tech*
185
+ *morph-spec v8.4.0 by Polymorphism Tech*
@@ -129,14 +129,20 @@ morph-spec verify my-feature T001 # scoped to one task
129
129
 
130
130
  Not every change needs the full pipeline walked above. Every feature is born with a **work type** that dimensions its pipeline:
131
131
 
132
+ Every feature is also born with an **identity** — a `--description` (the one field a human must supply; `create` refuses a nameless scaffold without it) and a `name` (defaults to the slug):
133
+
132
134
  ```bash
133
- morph-spec create my-feature --type feature # default: full proposal → [uiux] → plan → implement → review
134
- morph-spec create my-fix --type bug # lean proposal/plan
135
- morph-spec create my-chore --type chore # skips straight to implement (Gates 1/2 stamped by policy)
136
- morph-spec create my-hotfix --type hotfix # scout → surgical fix → test loop; Gate 3 always pauses for a human
135
+ # full: proposal → [uiux] → plan → implement → review
136
+ morph-spec create my-feature --type feature --description "what & why"
137
+ # bug: lean proposal/plan
138
+ morph-spec create my-fix --type bug --description "what broke"
139
+ # chore: skips straight to implement (Gates 1/2 stamped by policy)
140
+ morph-spec create my-chore --type chore --description "the tidy-up"
141
+ # hotfix: scout → surgical fix → test loop; Gate 3 always pauses for a human
142
+ morph-spec create my-hotfix --type hotfix --description "the incident"
137
143
  ```
138
144
 
139
- You can also let the deterministic detector classify a free-text request instead of picking a type by hand:
145
+ You can also let the deterministic detector classify a free-text request instead of picking a type by hand (`--request` doubles as the description):
140
146
 
141
147
  ```bash
142
148
  morph-spec create my-feature --request "produção caiu, corrigir urgente"
@@ -157,7 +163,7 @@ Production incidents run through the dedicated `/morph-hotfix my-feature` slash
157
163
  Independent features can be developed in parallel, each isolated in its own git worktree — one feature, one worktree, one Claude Code instance:
158
164
 
159
165
  ```bash
160
- morph-spec create my-feature --worktree # birth the feature already isolated
166
+ morph-spec create my-feature --description "what & why" --worktree # birth the feature already isolated
161
167
  # or, for an existing feature:
162
168
  morph-spec worktree setup my-feature # creates .worktrees/my-feature/ on branch morph/my-feature
163
169
  ```
@@ -240,4 +246,4 @@ morph-spec doctor
240
246
 
241
247
  ---
242
248
 
243
- *morph-spec v8.3.0 by Polymorphism Tech*
249
+ *morph-spec v8.4.0 by Polymorphism Tech*
@@ -47,7 +47,9 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
47
47
  - `morph-spec dag <feature>` — análise do DAG de tasks.json com decisão sequencial vs paralelo **ponderada por effort** (raízes S vão inline, nunca ganham agente próprio).
48
48
  - `morph-spec score <feature> <task> <score> [--dims a,c,q,t]` — grava `taskScores` no feature.json com timestamp server-side; **nunca edite taskScores à mão**. Em PowerShell, cite o valor de `--dims` entre aspas (`--dims "9.5,9,9,9"`) — sem aspas, a lista pode chegar como tokens separados; o parser aceita ambas as formas, mas citar evita qualquer ambiguidade.
49
49
  - `morph-spec gate-decision <feature> <gate> --pause <bool> [--reason <txt>] [--signals <json>]` — grava `gateDecisions` no feature.json com timestamp server-side (shape em `framework/schemas/feature.schema.json`); **nunca edite gateDecisions à mão** — o Edit manual colide com o hook state-sync.
50
- - `morph-spec create <feature> --type <feature|bug|chore|hotfix> [--request "<txt>"] [--worktree]` — cria a estrutura da feature + `feature.json` canônico explicitamente (nascimento explícito no início da proposal; o hook state-sync continua cobrindo o caminho implícito como fallback). `--type` classifica direto; `--request "<pedido>"` roda o detector determinístico (empate/sem sinal sai com exit 2 e candidatos — a LLM decide e re-roda com `--type`); `--worktree` já nasce a feature no seu worktree próprio.
50
+ - `morph-spec create <feature> --description "<o quê/porquê>" --type <feature|bug|chore|hotfix> [--request "<txt>"] [--name "<nome>"] [--worktree]` — cria a estrutura da feature + `feature.json` canônico explicitamente (nascimento explícito no início da proposal; o hook state-sync continua cobrindo o caminho implícito como fallback). **Identidade no nascimento:** `name` (default = slug) e `description` são carimbados; a `--description` é **obrigatória** numa feature nova (ou `--request`, que a semeia) — sem nenhuma das duas o `create` recusa (fim dos scaffolds anônimos). `--type` classifica direto; `--request "<pedido>"` roda o detector determinístico (empate/sem sinal sai com exit 2 e candidatos — a LLM decide e re-roda com `--type` + `--request`/`--description`); `--worktree` já nasce a feature no seu worktree próprio.
51
+ - `morph-spec list [--all] [--json]` — o quadro de auditoria: uma linha por feature × workType × status × fase × gates G1/G2/G3 × descrição, com `⚠` para inconsistências (sem descrição, ordem de gate). `--all` inclui as arquivadas; `--json` para scripting. É o complemento do `delete` (enxergar o que está morto antes de remover).
52
+ - `morph-spec delete <feature> [--archive] [--force]` — remove uma feature. Hard por padrão (pasta apagada, git-aware, + entrada podada do índice raiz para não ressuscitar); `--archive` move para `.morph/archive` com status `abandoned` em vez de apagar. Recusa se algum gate estiver aprovado, a menos que `--force`. Use para faxina de scaffolds mortos e drafts abandonados (`archive` é para trabalho concluído; `delete` é para trabalho morto).
51
53
  - `morph-spec retype <feature> <newType>` — muda o tipo de trabalho mid-flight; desfaz só os gates carimbados por política, preserva aprovações humanas; bloqueado após o Gate 3.
52
54
  - `morph-spec verify <feature> [task] [--json]` — cadeia determinística build+testes+validadores (código antes do juiz LLM); escopo de task quando informado, senão a feature inteira.
53
55
  - `morph-spec worktree setup|link|finish|list` — gerencia o ciclo do worktree por feature (`setup` cria `.worktrees/{f}/` na branch `morph/{f}`, aloca o bloco de portas e escreve `.morph/worktree.env`; `link` religa infra compartilhada; `finish` faz merge `--no-ff`, limpa e libera o bloco; `list` mostra fase/gate de cada worktree ativo).
@@ -70,9 +72,9 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
70
72
 
71
73
  ## Roteamento por tipo de trabalho
72
74
 
73
- Toda feature nasce com um `workType` (`morph-spec create <f> --type <tipo>` ou classificado de `--request "<pedido>"`) que dimensiona o pipeline: `feature` roda o fluxo completo (proposal → [uiux] → plan → implement → review); `bug` roda uma versão enxuta (proposal/plan lean); `chore` pula direto para implement (Gates 1/2 carimbados por política no nascimento); `hotfix` roda scout → fix cirúrgico → test loop → ship. O Gate 3 do `hotfix` **pausa sempre para um humano**, mesmo em `trust: auto` — o CLI recusa `morph-spec approve <f> review --mode auto` nesse tipo. Detalhes em `.morph/framework/MORPH.md` §2.1 e §5.
75
+ Toda feature nasce com um `workType` (`morph-spec create <f> --description "<o quê/porquê>" --type <tipo>` ou classificado de `--request "<pedido>"`, que também semeia a descrição obrigatória) que dimensiona o pipeline: `feature` roda o fluxo completo (proposal → [uiux] → plan → implement → review); `bug` roda uma versão enxuta (proposal/plan lean); `chore` pula direto para implement (Gates 1/2 carimbados por política no nascimento); `hotfix` roda scout → fix cirúrgico → test loop → ship. O Gate 3 do `hotfix` **pausa sempre para um humano**, mesmo em `trust: auto` — o CLI recusa `morph-spec approve <f> review --mode auto` nesse tipo. Detalhes em `.morph/framework/MORPH.md` §2.1 e §5.
74
76
 
75
- Features independentes podem rodar em paralelo, cada uma isolada num git worktree (`morph-spec create <f> --worktree` ou `morph-spec worktree setup <f>`) — uma feature por worktree, uma instância de Claude Code por worktree. Cada worktree tem seu **bloco de portas** próprio (`.morph/worktree.env` — suba dev servers SEMPRE nelas, nunca nas portas padrão) e seu **handoff.md** de retomada. `feature.json` viaja commitado na branch `morph/{f}`; ao fechar, aprove o Gate 3 dentro do worktree e rode `morph-spec worktree finish <f>` seguido de `morph-spec archive <f>` na raiz. Visão agregada: `morph-spec fleet`. Detalhes em `.morph/framework/MORPH.md` §2.2.
77
+ Features independentes podem rodar em paralelo, cada uma isolada num git worktree (`morph-spec create <f> --description "<o quê/porquê>" --worktree` ou `morph-spec worktree setup <f>`) — uma feature por worktree, uma instância de Claude Code por worktree. Cada worktree tem seu **bloco de portas** próprio (`.morph/worktree.env` — suba dev servers SEMPRE nelas, nunca nas portas padrão) e seu **handoff.md** de retomada. `feature.json` viaja commitado na branch `morph/{f}`; ao fechar, aprove o Gate 3 dentro do worktree e rode `morph-spec worktree finish <f>` seguido de `morph-spec archive <f>` na raiz. Visão agregada: `morph-spec fleet`. Detalhes em `.morph/framework/MORPH.md` §2.2.
76
78
 
77
79
  ## Personas (em `.claude/agents/`)
78
80
 
@@ -97,7 +97,7 @@ Não avance de fase sem o gate correspondente ter sido confirmado.
97
97
 
98
98
  ### 2.1 Roteamento por tipo de trabalho
99
99
 
100
- Nem todo trabalho merece o pipeline completo. Cada feature nasce com um **workType** (`morph-spec create <f> --type <tipo>` ou classificado de `--request "<pedido>"`), e o tipo dimensiona o pipeline:
100
+ Nem todo trabalho merece o pipeline completo. Cada feature nasce com **identidade** (`name` = slug por padrão + `description` obrigatória) e um **workType** (`morph-spec create <f> --description "<o quê/porquê>" --type <tipo>`, ou classificado de `--request "<pedido>"` — que também semeia a descrição), e o tipo dimensiona o pipeline:
101
101
 
102
102
  | Tipo | Pipeline | Gates |
103
103
  |------|----------|-------|
@@ -114,7 +114,7 @@ Nem todo trabalho merece o pipeline completo. Cada feature nasce com um **workTy
114
114
 
115
115
  ### 2.2 Desenvolvimento paralelo por worktree (frota)
116
116
 
117
- Features independentes podem ser desenvolvidas **em paralelo, cada uma no seu git worktree**, com uma instância de Claude Code por worktree. `morph-spec create <f> --worktree` (ou `morph-spec worktree setup <f>`) cria `.worktrees/{f}/` na branch `morph/{f}`. Contrato:
117
+ Features independentes podem ser desenvolvidas **em paralelo, cada uma no seu git worktree**, com uma instância de Claude Code por worktree. `morph-spec create <f> --description "<o quê/porquê>" --worktree` (ou `morph-spec worktree setup <f>`) cria `.worktrees/{f}/` na branch `morph/{f}`. Contrato:
118
118
 
119
119
  - **Uma feature = um worktree = uma instância.** Nunca abra duas instâncias na mesma feature (o setup recusa com exit 3).
120
120
  - O estado autoritativo (`feature.json`) viaja **commitado na branch** `morph/{f}`. O `state.json` local do worktree é um índice derivável — materializado por self-heal, nunca sincronizado à mão.
@@ -16,7 +16,7 @@ Resolve uma queda ou bug crítico de produção com prioridade máxima em veloci
16
16
 
17
17
  ## Fluxo
18
18
 
19
- 1. **Nascimento:** `morph-spec create {feature-name} --type hotfix` — carimba os gates `proposal`/`plan` por política e cria `0-proposal/`, `2-plan/`, `3-implement/` num passo só.
19
+ 1. **Nascimento:** `morph-spec create {feature-name} --type hotfix --description "<incidente>"` — carimba `name`+`description` (obrigatória) e os gates `proposal`/`plan` por política e cria `0-proposal/`, `2-plan/`, `3-implement/` num passo só.
20
20
  2. **Scout obrigatório:** despacha `subagent_type: "morph-scout"` (fallback `general-purpose` + persona colada) para localizar root cause e blast radius antes de qualquer fix.
21
21
  3. **Hotfix brief:** escreve `0-proposal/proposal.md` curto (sintoma/root cause/blast radius/plano do fix) e `2-plan/tasks.json` mínimo (T1 fix, T2 verify/regressão).
22
22
  4. **Fix cirúrgico:** implementa a mudança mínima que resolve o sintoma — sem refatoração, sem hardening fora de escopo.
@@ -66,8 +66,9 @@ function registerFeatureIfMissing(state, featureName) {
66
66
  if (!state.features) state.features = {};
67
67
  if (state.features[featureName]) return false;
68
68
 
69
- // Canonical object-form gates (incl. optional uiux) — no boolean drift.
70
- state.features[featureName] = createDefaultFeature();
69
+ // Canonical object-form gates (incl. optional uiux) + stamped identity
70
+ // (name=slug, workType, createdAt) — no anonymous shell, no boolean drift.
71
+ state.features[featureName] = createDefaultFeature(featureName);
71
72
  return true;
72
73
  }
73
74
 
@@ -114,6 +115,15 @@ function gateFromQuestion(question) {
114
115
  return null;
115
116
  }
116
117
 
118
+ // Gate monotonicity — mirror of src/core/state/gate-order.js (hooks are
119
+ // installed standalone and cannot import from src/). Keep in sync:
120
+ // proposal → plan → review; uiux requires proposal.
121
+ const GATE_PREREQ = { proposal: [], uiux: ['proposal'], plan: ['proposal'], review: ['plan'] };
122
+ function unmetGatePrereqs(gate, gates) {
123
+ const isApproved = (g) => gates?.[g]?.approved === true || gates?.[g] === true;
124
+ return (GATE_PREREQ[gate] || []).filter((p) => !isApproved(p));
125
+ }
126
+
117
127
  function handleWriteEdit(input, cwd) {
118
128
  const filePath = input.tool_input?.file_path;
119
129
  const featureName = extractFeatureName(filePath);
@@ -139,23 +149,38 @@ function handleWriteEdit(input, cwd) {
139
149
 
140
150
  function handleAskUserQuestion(input, cwd) {
141
151
  const answers = input.tool_response?.answers ?? input.tool_response?.responses ?? {};
142
- if (typeof answers !== 'object' || answers === null) return false;
152
+ if (typeof answers !== 'object' || answers === null) return null;
143
153
 
144
154
  const state = loadState(cwd);
145
- if (!state) return false;
155
+ if (!state) return null;
146
156
 
147
157
  const featureName = findActiveFeature(state);
148
- if (!featureName) return false;
158
+ if (!featureName) return null;
149
159
 
160
+ const warnings = [];
150
161
  for (const [question, answer] of Object.entries(answers)) {
151
162
  if (!isApprovalAnswer(answer)) continue;
152
163
  const gate = gateFromQuestion(question);
153
164
  if (!gate) continue;
154
165
 
155
- const existing = state.features[featureName].approvalGates?.[gate];
166
+ const gates = state.features[featureName].approvalGates;
167
+ const existing = gates?.[gate];
156
168
  const alreadyApproved = existing === true || existing?.approved === true;
157
169
  if (alreadyApproved) continue;
158
170
 
171
+ // Gate monotonicity: fail-open. The human said "aprovado", so we DO approve
172
+ // (never block a human in the loop), but if a predecessor gate is still
173
+ // pending we surface a non-blocking warning — the CLI `morph-spec approve`
174
+ // hard-refuses the same shape; here we alert so the operator can reconcile.
175
+ const unmet = unmetGatePrereqs(gate, gates);
176
+ if (unmet.length) {
177
+ warnings.push(
178
+ `⚠️ [morph-spec] GATE FORA DE ORDEM — o Gate "${gate}" de "${featureName}" foi aprovado, ` +
179
+ `mas o(s) gate(s) anterior(es) ${unmet.join(', ')} não estão aprovados (ordem: ${unmet.join(' → ')} → ${gate}). ` +
180
+ `Aprove os gates anteriores ou reveja com 'morph-spec doctor'.`
181
+ );
182
+ }
183
+
159
184
  // An "aprovado" answer to AskUserQuestion is a human approving in the loop.
160
185
  // Route through the shared canonical gate writer so the hook and the CLI
161
186
  // (`morph-spec approve`) produce one consistent shape
@@ -163,7 +188,7 @@ function handleAskUserQuestion(input, cwd) {
163
188
  // and a manual approval stays distinguishable from a policy auto-approval.
164
189
  setGateApproved(featureName, gate, { approvedBy: 'manual' }, cwd);
165
190
  }
166
- return true;
191
+ return warnings.length ? warnings.join('\n') : null;
167
192
  }
168
193
 
169
194
  async function main() {
@@ -176,17 +201,24 @@ async function main() {
176
201
  const input = readStdin();
177
202
  const toolName = input.tool_name;
178
203
 
204
+ let warning = null;
179
205
  try {
180
206
  if (toolName === 'Write' || toolName === 'Edit') {
181
207
  handleWriteEdit(input, cwd);
182
208
  } else if (toolName === 'AskUserQuestion') {
183
- handleAskUserQuestion(input, cwd);
209
+ warning = handleAskUserQuestion(input, cwd);
184
210
  }
185
211
  } catch {
186
212
  // fail-open
187
213
  }
188
214
 
189
- exit();
215
+ // A non-blocking order-violation warning is surfaced as injected context
216
+ // (hookSpecificOutput.additionalContext); everything else exits silently.
217
+ if (warning) {
218
+ exit({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: warning } });
219
+ } else {
220
+ exit();
221
+ }
190
222
  }
191
223
 
192
224
  main().catch(() => exit());
@@ -91,7 +91,7 @@ export function persistFeature(featureName, feature, projectPath) {
91
91
  */
92
92
  export function setGateApproved(featureName, gate, meta = {}, projectPath) {
93
93
  try {
94
- const feature = readFeatureFile(featureName, projectPath) || createDefaultFeature();
94
+ const feature = readFeatureFile(featureName, projectPath) || createDefaultFeature(featureName);
95
95
  const now = new Date().toISOString();
96
96
  feature.approvalGates = feature.approvalGates || {};
97
97
  feature.approvalGates[gate] = {
@@ -106,11 +106,22 @@ export function setGateApproved(featureName, gate, meta = {}, projectPath) {
106
106
  }
107
107
  }
108
108
 
109
- /** Default feature shape (canonical object-form gates, incl. optional uiux). */
110
- export function createDefaultFeature() {
109
+ /**
110
+ * Default feature shape (canonical object-form gates, incl. optional uiux).
111
+ * Stamps identity — name (the slug), workType, createdAt — so the implicit
112
+ * birth path (state-sync registering a feature on the first Write) never
113
+ * produces an anonymous {status,updatedAt} shell. `description` stays absent
114
+ * (a hook can't invent it); `list`/`doctor` flag its absence. Mirrors the CLI
115
+ * createFeature's stamping.
116
+ *
117
+ * @param {string} [featureName] - the feature slug, stamped as `name`
118
+ */
119
+ export function createDefaultFeature(featureName) {
111
120
  const now = new Date().toISOString();
112
121
  return {
122
+ ...(featureName ? { name: featureName } : {}),
113
123
  status: 'in_progress',
124
+ workType: 'feature',
114
125
  createdAt: now,
115
126
  updatedAt: now,
116
127
  approvalGates: {
@@ -5,10 +5,18 @@
5
5
  "type": "object",
6
6
  "required": ["status", "approvalGates"],
7
7
  "properties": {
8
+ "name": {
9
+ "type": "string",
10
+ "description": "Human-readable feature name, stamped at birth by `morph-spec create` (defaults to the slug) and by the state-sync hook's implicit registration. Kept out of the schema's `required` array — adding it there would invalidate every feature.json that predates identity stamping; instead `create` enforces it at birth and `morph-spec doctor`/`list` flag its absence leniently."
11
+ },
12
+ "description": {
13
+ "type": "string",
14
+ "description": "What/why of the feature. The one identity field a human must supply — `morph-spec create` refuses a new feature without it (--description, or --request which seeds it). Absent on features born via the implicit hook path; surfaced as a warning by `doctor`/`list`, never a hard read-time rejection (see name)."
15
+ },
8
16
  "status": {
9
17
  "type": "string",
10
- "enum": ["draft", "in_progress", "completed"],
11
- "description": "Feature lifecycle status. `completed` is stamped by `morph-spec archive` together with archivedAt."
18
+ "enum": ["draft", "in_progress", "completed", "abandoned"],
19
+ "description": "Feature lifecycle status. `completed` is stamped by `morph-spec archive` together with archivedAt. `abandoned` is stamped by `morph-spec delete --archive` together with abandonedAt (dead scaffold / killed draft, kept in .morph/archive)."
12
20
  },
13
21
  "createdAt": { "type": "string", "format": "date-time" },
14
22
  "updatedAt": {
@@ -21,6 +29,11 @@
21
29
  "format": "date-time",
22
30
  "description": "Stamped by `morph-spec archive`."
23
31
  },
32
+ "abandonedAt": {
33
+ "type": "string",
34
+ "format": "date-time",
35
+ "description": "Stamped by `morph-spec delete --archive` when a dead/abandoned feature is moved to .morph/archive."
36
+ },
24
37
  "approvalGates": {
25
38
  "type": "object",
26
39
  "description": "One entry per gate. Written ONLY by `morph-spec approve`/`advance` (approvedBy: \"auto\") or the state-sync hook on a human \"aprovado\" answer (approvedBy: \"manual\").",
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
4
4
  version: core
5
5
  when-to-invoke: ao iniciar um novo projeto de LP com o template Umbraco genérico
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # create-umbraco-lp
@@ -4,7 +4,7 @@ description: Migra uma landing page existente (React/Vite, Next.js estático ou
4
4
  version: core
5
5
  when-to-invoke: ao receber instrução "use /migrate-lp-to-umbraco {projeto}" do usuário, ou quando o usuário descrever necessidade de migrar uma LP para Umbraco
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # migrate-lp-to-umbraco
@@ -4,7 +4,7 @@ description: Orquestra a execução autônoma de tasks de uma feature morph-spec
4
4
  version: core
5
5
  when-to-invoke: após Gate 2 aprovado, ao receber instrução "use /morph-apply" ou "implementar feature"
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-apply
@@ -4,7 +4,7 @@ description: Entende o negócio, os usuários e a arquitetura antes de qualquer
4
4
  version: core
5
5
  when-to-invoke: ao receber um novo request de feature, antes de morph-plan
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-brainstorming
@@ -4,7 +4,7 @@ description: Avalia a qualidade de uma tarefa ou fase com rubricas graduadas 0-1
4
4
  version: core
5
5
  when-to-invoke: ao completar uma tarefa ou ao finalizar uma fase de implementação
6
6
  user-invokable: false
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-eval
@@ -4,7 +4,7 @@ description: ADW cirúrgico de hotfix — resolve uma queda/bug crítico de prod
4
4
  version: core
5
5
  when-to-invoke: ao receber "/morph-hotfix {feature}", quando a morph-proposal classifica o pedido como workType hotfix (Passo 0), ou o usuário diz "produção caiu / hotfix / urgente"
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-hotfix
@@ -28,10 +28,10 @@ Resolver uma queda ou bug crítico de produção o mais rápido possível — pr
28
28
  ### 1. Nascimento da feature
29
29
 
30
30
  ```
31
- morph-spec create {feature} --type hotfix
31
+ morph-spec create {feature} --type hotfix --description "<o incidente: o que quebrou / sintoma>"
32
32
  ```
33
33
 
34
- Carimba os gates `proposal` e `plan` como `approvedBy: "policy:hotfix"` e cria as pastas `0-proposal/`, `2-plan/`, `3-implement/` num passo só — a feature já nasce em `implement` (fase derivada é gate-aware). Se a feature já existe, siga adiante sem recriar.
34
+ Carimba `name` (o slug) + `description` (obrigatória no nascimento — descreva o incidente) e os gates `proposal`/`plan` como `approvedBy: "policy:hotfix"`, e cria as pastas `0-proposal/`, `2-plan/`, `3-implement/` num passo só — a feature já nasce em `implement` (fase derivada é gate-aware). Se a feature já existe, siga adiante sem recriar.
35
35
 
36
36
  ### 2. Scout — mapa do território (obrigatório)
37
37
 
@@ -4,7 +4,7 @@ description: Guia a execução de cada tarefa de implementação com standard e
4
4
  version: core
5
5
  when-to-invoke: ao iniciar cada task de implementação listada em tasks.json
6
6
  user-invokable: false
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-implement
@@ -4,7 +4,7 @@ description: Guia a produção dos três artefatos de planejamento de uma featur
4
4
  version: core
5
5
  when-to-invoke: ao iniciar a fase plan de uma feature, após o Gate 1 (proposal aprovado)
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-plan
@@ -4,7 +4,7 @@ description: Orquestra a fase de proposta+plano de uma feature morph-spec. Encad
4
4
  version: core
5
5
  when-to-invoke: ao receber instrução "use /morph-proposal" do usuário, ou ao iniciar uma nova feature
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-proposal
@@ -38,10 +38,10 @@ Antes de qualquer trabalho de negócio, classifique o pedido. Se `{feature-name}
38
38
  morph-spec create {feature-name} --request "<pedido do usuário>"
39
39
  ```
40
40
 
41
- (ou `--type <tipo>` diretamente quando o tipo é óbvio pelo pedido — ex.: o usuário já diz "isso é um hotfix").
41
+ (ou `--type <tipo> --description "<o quê/porquê>"` diretamente quando o tipo é óbvio pelo pedido — ex.: o usuário já diz "isso é um hotfix"). O `--request` **serve de semente da descrição**: toda feature nasce com `name` (o slug) e `description` (obrigatória) carimbados. Sem `--request` nem `--description`, o `create` recusa — nada de scaffolds anônimos.
42
42
 
43
43
  - **Classificação `high` confidence** — a feature nasce com o `workType` decidido pelo detector determinístico (keywords: `hotfix` — "produção caiu/urgente/incident/p0"; `bug` — "bug/corrigir/exception/regressão"; `chore` — "typo/rename/bump/config/docs/cleanup"). Prossiga para o branch correspondente abaixo.
44
- - **Exit 2** (empate `tie` ou nenhum sinal `none`) — o comando sai sem criar a feature e imprime `{ needsDecision, candidates, matched }`. **Você é o roteador**: decida o tipo entre os candidatos e re-rode com `morph-spec create {feature-name} --type <tipo>`. Só use `AskUserQuestion` para o usuário quando o pedido for genuinamente indeterminado — a maioria dos empates o roteador resolve sozinho pelo contexto da conversa.
44
+ - **Exit 2** (empate `tie` ou nenhum sinal `none`) — o comando sai sem criar a feature e imprime `{ needsDecision, candidates, matched }`. **Você é o roteador**: decida o tipo entre os candidatos e re-rode com `morph-spec create {feature-name} --type <tipo> --request "<pedido do usuário>"` (o `--request` re-passado carimba a descrição). Só use `AskUserQuestion` para o usuário quando o pedido for genuinamente indeterminado — a maioria dos empates o roteador resolve sozinho pelo contexto da conversa.
45
45
 
46
46
  **Branches por tipo** (dimensionamento completo em `MORPH.md` §2.1):
47
47
 
@@ -55,7 +55,7 @@ morph-spec create {feature-name} --request "<pedido do usuário>"
55
55
  - Se argumento ausente: perguntar via `AskUserQuestion` (1 pergunta, header "Feature").
56
56
  - Verificar `.morph/features/{feature-name}/`:
57
57
  - Se `0-proposal/proposal.md` existe e tem conteúdo: pular para passo 3.
58
- - Caso contrário: rode `morph-spec create {feature-name}` — cria `.morph/features/{feature-name}/0-proposal/` e o `feature.json` canônico (shape em `.morph/framework/schemas/feature.schema.json`) num passo só, server-side. Não crie a estrutura à mão nem dependa do nascimento implícito via hook no primeiro Write. Depois siga o passo 2.
58
+ - Caso contrário: rode `morph-spec create {feature-name} --description "<resumo do pedido do usuário>"` — cria `.morph/features/{feature-name}/0-proposal/` e o `feature.json` canônico (shape em `.morph/framework/schemas/feature.schema.json`) num passo só, server-side, com `name`+`description` carimbados. A `--description` é **obrigatória** no nascimento (ou `--request`, que a semeia). Não crie a estrutura à mão nem dependa do nascimento implícito via hook no primeiro Write. Depois siga o passo 2.
59
59
 
60
60
  ### 2. Invocar `Skill(morph-brainstorming)`
61
61
 
@@ -4,7 +4,7 @@ description: Conduz a fase de revisão e produz o review-report.md para o Gate 3
4
4
  version: core
5
5
  when-to-invoke: ao iniciar a fase review, após todas as tasks de tasks.json estarem com status `done`
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-review
@@ -4,7 +4,7 @@ description: Varre o codebase existente, detecta stack atual, compara com o stac
4
4
  version: core
5
5
  when-to-invoke: ao iniciar uma feature em projeto brownfield (codebase já existente)
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-stack-scan
@@ -4,7 +4,7 @@ description: Localiza e carrega um standard específico do STANDARDS.json sob de
4
4
  version: core
5
5
  when-to-invoke: quando precisar consultar um standard específico durante a implementação
6
6
  user-invokable: true
7
- cliVersion: "8.3.0"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-standards
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "8.3.0",
3
+ "version": "8.4.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",