@polymorphism-tech/morph-spec 8.3.0 → 8.5.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 (43) hide show
  1. package/README.md +11 -8
  2. package/bin/morph-spec.js +37 -11
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +12 -7
  5. package/docs/QUICKSTART.md +19 -10
  6. package/framework/CLAUDE.md +9 -6
  7. package/framework/MORPH.md +5 -3
  8. package/framework/commands/morph-archive.md +11 -4
  9. package/framework/commands/morph-hotfix.md +1 -1
  10. package/framework/hooks/claude-code/core/state-sync.js +41 -9
  11. package/framework/hooks/shared/state-writer.js +14 -3
  12. package/framework/schemas/feature.schema.json +17 -4
  13. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  14. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  15. package/framework/skills/core/morph-apply/SKILL.md +8 -5
  16. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  17. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  18. package/framework/skills/core/morph-hotfix/SKILL.md +5 -5
  19. package/framework/skills/core/morph-implement/SKILL.md +1 -1
  20. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  21. package/framework/skills/core/morph-proposal/SKILL.md +4 -4
  22. package/framework/skills/core/morph-review/SKILL.md +4 -2
  23. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  24. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  25. package/package.json +1 -1
  26. package/src/commands/project/doctor.js +28 -0
  27. package/src/commands/project/worktree.js +154 -357
  28. package/src/commands/state/advance.js +17 -0
  29. package/src/commands/state/approve.js +13 -0
  30. package/src/commands/state/archive.js +148 -70
  31. package/src/commands/state/create.js +67 -3
  32. package/src/commands/state/delete.js +143 -0
  33. package/src/commands/state/finish.js +422 -0
  34. package/src/commands/state/list.js +110 -0
  35. package/src/core/state/feature-identity.js +36 -0
  36. package/src/core/state/gate-order.js +51 -0
  37. package/src/core/state/lifecycle-audit.js +79 -0
  38. package/src/core/state/state-manager.js +49 -14
  39. package/src/dashboard/scanner/state-scanner.js +1 -1
  40. package/src/lib/generators/handoff-generator.js +2 -2
  41. package/src/lib/git/default-branch.js +73 -0
  42. package/src/lib/worktree/env-allocator.js +1 -1
  43. package/src/utils/file-copier.js +2 -2
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.5.0
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -140,7 +140,7 @@ These commands are available inside Claude Code after init.
140
140
  | `/morph-apply {feature}` | Autonomous execution + review (Gate 3) |
141
141
  | `/morph-hotfix {feature}` | Surgical hotfix ADW — scout → fix → human Gate 3 → ship |
142
142
  | `/morph-status [feature]` | Status of the active (or specified) feature |
143
- | `/morph-archive {feature}` | Archive a completed feature (after Gate 3) |
143
+ | `/morph-archive {feature}` | Archive **without integrating** (legacy feature / cleanup); normal close is `morph-spec finish --pr\|--merge` |
144
144
  | `/morph-preflight` | Pre-deploy validation (specs, contracts, tests, infra) |
145
145
  | `/morph-troubleshoot [error]` | Root-cause diagnosis for .NET/Next.js errors |
146
146
 
@@ -167,9 +167,12 @@ morph-spec status {feature} # Feature status dashboard
167
167
  morph-spec approve {feature} {gate} # Approve a phase gate (proposal, plan, review)
168
168
  morph-spec advance {feature} # Approve the current gate and scaffold the next phase folder
169
169
  morph-spec approval-status {feature} # Show approval status for all gates
170
- morph-spec archive {feature} # Archive a completed feature (after Gate 3)
170
+ morph-spec finish {feature} --pr|--merge # Close a feature: archive + integrate (PR or local merge) + teardown
171
+ morph-spec archive {feature} # Archive WITHOUT integrating (legacy feature / cleanup)
171
172
  ```
172
173
 
174
+ `finish` is the normal close for both flows: it archives (committing the move inside `morph/{feature}`), then integrates — `--merge` merges `--no-ff` into the default branch locally; `--pr` pushes the branch and runs `gh pr create` (needs `origin` + authenticated `gh`) — and tears down the worktree if there is one. Plain `archive` is reserved for legacy features (born before every feature had a `morph/{feature}` branch) and cleanup.
175
+
173
176
  Task progress within a feature is tracked with the native `TaskCreate`/`TaskUpdate` tools, persisted to `tasks.json` — there is no separate CLI command for starting or completing a task.
174
177
 
175
178
  ### Validation
@@ -184,8 +187,8 @@ morph-spec verify {feature} [task] # Deterministic build+tests+validators ch
184
187
  ### Work-type routing
185
188
 
186
189
  ```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
190
+ morph-spec create {feature} --type feature|bug|chore|hotfix --description "<what/why>" # Explicit birth: type dimensions the pipeline, description is required
191
+ morph-spec create {feature} --request "<free text>" # Detector classifies the type AND seeds the description
189
192
  morph-spec retype {feature} {newType} # Change work type mid-flight (blocked after Gate 3)
190
193
  morph-spec dag {feature} [--mermaid] # DAG dispatch decision; --mermaid emits a flowchart
191
194
  ```
@@ -193,11 +196,11 @@ morph-spec dag {feature} [--mermaid] # DAG dispatch d
193
196
  ### Worktrees (parallel development)
194
197
 
195
198
  ```bash
196
- morph-spec create {feature} --worktree # Birth the feature already isolated in its own worktree
199
+ morph-spec create {feature} --description "<what/why>" --worktree # Birth the feature already isolated in its own worktree
197
200
  morph-spec worktree setup {feature} # Create .worktrees/{feature}/ on branch morph/{feature}
198
201
  morph-spec worktree link [--all] # Relink shared infra (node_modules, .claude, .morph/framework)
199
- morph-spec worktree finish {feature} # Merge --no-ff back to the primary root, clean up (requires Gate 3)
200
202
  morph-spec worktree list [--json] # List active feature worktrees with phase/gate status
203
+ # Close a worktree feature with `morph-spec finish {feature} --pr|--merge` (see Feature workflow) — it absorbed `worktree finish`.
201
204
  ```
202
205
 
203
206
  ---
@@ -365,4 +368,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
365
368
 
366
369
  ---
367
370
 
368
- *morph-spec v8.3.0 by [Polymorphism Tech](https://polymorphism.tech)*
371
+ *morph-spec v8.5.0 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -23,18 +23,21 @@ import { installPluginCommand } from '../src/commands/project/install-plugin-cmd
23
23
  import { updateCommand } from '../src/commands/project/update.js';
24
24
  import { doctorCommand } from '../src/commands/project/doctor.js';
25
25
  import { statusCommand } from '../src/commands/project/status.js';
26
- import { worktreeSetupCommand, worktreeLinkCommand, worktreeFinishCommand, worktreeListCommand } from '../src/commands/project/worktree.js';
26
+ import { worktreeSetupCommand, worktreeLinkCommand, worktreeListCommand } from '../src/commands/project/worktree.js';
27
27
  import { fleetCommand } from '../src/commands/project/fleet.js';
28
28
 
29
29
  // === Approval gates (human-only) ===
30
30
  import { approveCommand, approvalStatusCommand, unapproveCommand } from '../src/commands/state/approve.js';
31
31
  import { advanceCommand } from '../src/commands/state/advance.js';
32
32
  import { archiveCommand } from '../src/commands/state/archive.js';
33
+ import { finishCommand } from '../src/commands/state/finish.js';
33
34
  import { scoreCommand } from '../src/commands/state/score.js';
34
35
  import { gateDecisionCommand } from '../src/commands/state/gate-decision.js';
35
36
  import { createCommand } from '../src/commands/state/create.js';
36
37
  import { retypeCommand } from '../src/commands/state/retype.js';
37
38
  import { handoffCommand } from '../src/commands/state/handoff.js';
39
+ import { listCommand } from '../src/commands/state/list.js';
40
+ import { deleteCommand } from '../src/commands/state/delete.js';
38
41
 
39
42
  // === Tasks ===
40
43
  import { dagCommand } from '../src/commands/tasks/dag.js';
@@ -126,13 +129,22 @@ program
126
129
  .option('-v, --verbose', 'Show detailed output')
127
130
  .action(statusCommand);
128
131
 
132
+ program
133
+ .command('list')
134
+ .description('List every feature × status × gates (G1/G2/G3) × workType — the audit board')
135
+ .option('--all', 'Include archived features (.morph/archive)')
136
+ .option('--archived', 'Include archived features (alias of --all)')
137
+ .option('--json', 'Output as JSON')
138
+ .action((options) => listCommand(options));
139
+
129
140
  // ─── Approval gates (dev-only) ────────────────────────────────────────────
130
141
  program
131
142
  .command('approve <feature> <gate>')
132
- .description('Approve a phase gate (proposal, plan, review)')
143
+ .description('Approve a phase gate (proposal, plan, review). Enforces gate order (N requires N-1)')
133
144
  .option('--approver <name>', 'Name of approver (default: current user when interactive, "auto" otherwise)')
134
145
  .option('--mode <mode>', 'Approval mode: "user" or "auto" (default: detected from TTY)')
135
146
  .option('--notes <notes>', 'Approval notes')
147
+ .option('--force', 'Approve out of order (bypass the gate-monotonicity check)')
136
148
  .action((feature, gate, options) => approveCommand(feature, gate, options));
137
149
 
138
150
  program
@@ -142,15 +154,35 @@ program
142
154
  .option('--mode <mode>', 'Approval mode: "user" or "auto" (default: detected from TTY)')
143
155
  .option('--design', 'From proposal, advance into the optional design (uiux) phase instead of plan')
144
156
  .option('--notes <notes>', 'Approval notes')
157
+ .option('--force', 'Advance even if a prerequisite gate is unapproved (out-of-order recovery)')
145
158
  .action((feature, options) => advanceCommand(feature, options));
146
159
 
147
160
  program
148
161
  .command('archive <feature>')
149
- .description('Archive a completed feature (atomic, git-aware): move folder + update state')
162
+ .description('Archive a completed feature (atomic, git-aware): move folder + update state. Archive WITHOUT integrating (legacy features, cleanup) — the close path is `finish`')
150
163
  .option('--no-git', 'Skip git staging of the move')
151
164
  .option('--force', 'Archive even if Gate 3 (review) is not approved')
152
165
  .action((feature, options) => archiveCommand(feature, options));
153
166
 
167
+ program
168
+ .command('finish <feature>')
169
+ .description('Close a feature: archive → integrate. --merge merges morph/{f} into the default (local); --pr pushes it + opens a PR. Works with and without a worktree')
170
+ .option('--pr', 'Push morph/{feature} and open a PR against the default branch (no local merge)')
171
+ .option('--merge', 'Merge morph/{feature} into the default branch locally (no push)')
172
+ .option('--keep', 'Keep the worktree/branch after integrating (worktree flow only)')
173
+ .option('--force', 'Skip the Gate-3-approved precondition')
174
+ .option('--discard-ignored', 'Allow removing the worktree even though it holds gitignored files that exist nowhere else (e.g. .env) — they are destroyed')
175
+ .action((feature, options) => finishCommand(feature, options));
176
+
177
+ program
178
+ .command('delete <feature>')
179
+ .description('Delete a feature (hard by default; --archive marks it abandoned). Refuses if a gate is approved unless --force')
180
+ .option('--archive', 'Soft delete: move to .morph/archive with status "abandoned" instead of removing')
181
+ .option('--force', 'Delete even when a gate is approved')
182
+ .option('--no-git', 'Skip git staging of the removal')
183
+ .option('--json', 'Output as JSON')
184
+ .action((feature, options) => deleteCommand(feature, options));
185
+
154
186
  program
155
187
  .command('approval-status <feature>')
156
188
  .description('Show approval status for all gates')
@@ -178,6 +210,8 @@ program
178
210
  program
179
211
  .command('create <feature>')
180
212
  .description('Classify work type (--type/--request), scaffold + stamp policy gates, and create the canonical feature.json (explicit birth — see framework/schemas/feature.schema.json)')
213
+ .option('--name <name>', 'Human-readable feature name (default: the slug)')
214
+ .option('--description <text>', 'What/why of the feature — required at birth unless --request is given (it seeds the description)')
181
215
  .option('--status <status>', 'Initial feature status (default: in_progress)')
182
216
  .option('--type <type>', 'Work type: feature|bug|chore|hotfix (default: feature) — see src/core/work-types.js')
183
217
  .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)')
@@ -318,14 +352,6 @@ worktreeCommand
318
352
  .option('--all', 'Link all shared dirs, not just node_modules')
319
353
  .action((options) => worktreeLinkCommand(options));
320
354
 
321
- worktreeCommand
322
- .command('finish <feature>')
323
- .description('Merge the feature branch back into the primary root and clean up the worktree (requires Gate 3 approved)')
324
- .option('--keep', 'Keep the worktree and branch after merging')
325
- .option('--force', 'Skip the Gate-3-approved precondition')
326
- .option('--discard-ignored', 'Allow removing the worktree even though it holds gitignored files that exist nowhere else (e.g. .env) — they are destroyed')
327
- .action((feature, options) => worktreeFinishCommand(feature, options));
328
-
329
355
  worktreeCommand
330
356
  .command('list')
331
357
  .description('List active feature worktrees with their phase and gate status')
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "8.3.0",
3
+ "version": "8.5.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",
@@ -46,7 +46,7 @@ proposal → [uiux] → plan → implement → review
46
46
  | `/morph-hotfix {feature}` | Surgical hotfix ADW (scout → fix → human gate → ship) |
47
47
  | `/morph-apply {feature}` | Autonomous execution + review (Gate 3) |
48
48
  | `/morph-status [feature]` | Status of the active (or specified) feature |
49
- | `/morph-archive {feature}` | Archive a completed feature (after Gate 3) |
49
+ | `/morph-archive {feature}` | Archive **without integrating** (legacy feature / cleanup) — normal close is `morph-spec finish --pr\|--merge` |
50
50
  | `/morph-preflight` | Pre-deploy validation (specs, contracts, tests, infra) |
51
51
  | `/morph-troubleshoot [error]` | Root-cause diagnosis for .NET/Next.js errors |
52
52
 
@@ -59,18 +59,23 @@ morph-spec approve {feature} {gate} [--mode auto] [--approver name] [--notes tex
59
59
  morph-spec advance {feature} [--design] # approve current gate + scaffold next phase folder in one step
60
60
  morph-spec approval-status {feature} [--json]
61
61
  morph-spec unapprove {feature} {gate} [--reason text] # recovery only
62
- morph-spec archive {feature} [--force] [--no-git]
62
+ morph-spec finish {feature} --pr|--merge [--keep] [--force] [--discard-ignored] # normal close: archive + integrate + teardown
63
+ morph-spec archive {feature} [--force] [--no-git] # archive WITHOUT integrating (legacy feature / cleanup)
63
64
  ```
64
65
 
66
+ `finish` is the normal way to close a feature (both the worktree flow and the common flow): it archives (committing the move inside `morph/{feature}`), then integrates — `--merge` merges `--no-ff` into the default branch locally (no push); `--pr` pushes `morph/{feature}` + `gh pr create` (needs `origin` + authenticated `gh`). It then tears down the worktree and releases the port block. `archive` alone is reserved for legacy features (no `morph/{feature}` branch) and cleanup.
67
+
65
68
  ---
66
69
 
67
70
  ## CLI — feature birth & work-type routing
68
71
 
69
72
  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
73
 
74
+ Every feature is also born with an **identity**: `--description` is required (`create` refuses a nameless scaffold without it), `name` defaults to the slug.
75
+
71
76
  ```bash
72
- morph-spec create {feature} --type feature|bug|chore|hotfix
73
- morph-spec create {feature} --request "<free text>" # deterministic detector classifies the type
77
+ morph-spec create {feature} --type feature|bug|chore|hotfix --description "<what/why>"
78
+ morph-spec create {feature} --request "<free text>" # detector classifies the type AND seeds the description
74
79
  morph-spec retype {feature} {newType} # change type mid-flight (blocked after Gate 3)
75
80
  ```
76
81
 
@@ -111,11 +116,11 @@ morph-spec dag {feature} --mermaid # emit the tasks.json DAG as a Mermai
111
116
  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
117
 
113
118
  ```bash
114
- morph-spec create {feature} --worktree # birth the feature already isolated in its own worktree
119
+ morph-spec create {feature} --description "<what/why>" --worktree # birth the feature already isolated in its own worktree
115
120
  morph-spec worktree setup {feature} # create .worktrees/{feature}/ on branch morph/{feature}
116
121
  morph-spec worktree link [--all] # relink shared infra (node_modules[, .claude, .morph/framework])
117
- morph-spec worktree finish {feature} # merge --no-ff back to the primary root, clean up + release ports (requires Gate 3)
118
122
  morph-spec worktree list [--json] # active feature worktrees with phase/gate status
123
+ # Close a worktree feature with `morph-spec finish {feature} --pr|--merge` (see above) — it absorbed `worktree finish`.
119
124
  morph-spec handoff {feature} [--json] # regenerate the compressed session-handoff doc (pointers, not copies)
120
125
  morph-spec fleet [--json] # every active feature (root + worktrees) with phase/gates/ports/handoff/activity
121
126
  ```
@@ -180,4 +185,4 @@ Never edited directly — go through CLI commands or `morph-spec update` instead
180
185
 
181
186
  ---
182
187
 
183
- *morph-spec v8.3.0 by Polymorphism Tech*
188
+ *morph-spec v8.5.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,18 +163,21 @@ 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
  ```
164
170
 
165
- Open the new worktree directory in its own Claude Code instance and work normally — a SessionStart hook injects the worktree's feature identity into context automatically. When the feature is done:
171
+ Open the new worktree directory in its own Claude Code instance and work normally — a SessionStart hook injects the worktree's feature identity into context automatically. When the feature is done (Gate 3 approved), close it in one step:
166
172
 
167
173
  ```bash
168
- morph-spec worktree finish my-feature # merges --no-ff back into the primary root, then cleans up
169
- morph-spec archive my-feature # run at the primary root afterward
174
+ morph-spec finish my-feature --merge # archive + merge --no-ff into the default branch (local), tear down the worktree
175
+ # or, to open a PR instead of merging locally:
176
+ morph-spec finish my-feature --pr # archive + push morph/my-feature + gh pr create (keeps the branch the PR needs)
170
177
  ```
171
178
 
179
+ `finish` archives the feature (committing the move inside `morph/my-feature`) *and* integrates *and* tears down the worktree in one step — it replaces the old `worktree finish` + separate `archive`. The same command closes features developed **without** a worktree (they're born on `morph/{feature}` too).
180
+
172
181
  List every active worktree with its phase and gate status:
173
182
 
174
183
  ```bash
@@ -240,4 +249,4 @@ morph-spec doctor
240
249
 
241
250
  ---
242
251
 
243
- *morph-spec v8.3.0 by Polymorphism Tech*
252
+ *morph-spec v8.5.0 by Polymorphism Tech*
@@ -36,21 +36,24 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
36
36
  | `/morph-hotfix {feature}` | ADW cirúrgico de hotfix (scout → fix → gate humano → ship) |
37
37
  | `/morph-apply {feature}` | Execução autônoma + review (Gate 3) |
38
38
  | `/morph-status [feature]` | Status da feature ativa (ou a especificada) |
39
- | `/morph-archive {feature}` | Arquiva feature concluída (após Gate 3) — wrapper de `morph-spec archive` (atômico, git-aware) |
39
+ | `/morph-archive {feature}` | Arquiva-**sem-integrar** (features legadas/faxina) — wrapper de `morph-spec archive`. Fechamento normal = `morph-spec finish --pr\|--merge` |
40
40
  | `/morph-preflight` | Validação pré-deploy (specs, contratos, testes, infra) |
41
41
  | `/morph-troubleshoot [erro]` | Diagnóstico de erros .NET/Next.js com root-cause analysis |
42
42
 
43
43
  **Comandos CLI de gate e de estado** (a LLM os usa em vez de editar o estado à mão):
44
44
  - `morph-spec approve <feature> <gate> --mode auto` — aprova um gate (`proposal`/`plan`/`review`) em `trust: auto`.
45
45
  - `morph-spec advance <feature>` — aprova o gate da fase corrente **e** cria a pasta da próxima fase num passo só (`--design` toma o ramo opcional de uiux a partir da proposta).
46
- - `morph-spec archive <feature>` — arquiva a feature (atômico, git-aware); é o que `/morph-archive` chama.
46
+ - `morph-spec archive <feature>` — arquiva-**sem-integrar** (atômico, git-aware, commita o move escopado por pathspec): reservado a features legadas (sem branch `morph/{f}`) e faxina. O fechamento normal (arquiva **e** integra num passo) é `morph-spec finish`. É o que `/morph-archive` chama.
47
+ - `morph-spec finish <feature> --pr|--merge` — o **encerramento único** da feature (worktree ou comum): **arquiva** commitando `features/{f} → archive/{f}` dentro de `morph/{f}`, depois integra. `--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}`. Flags: `--keep` mantém o worktree, `--force` dispensa o Gate 3, `--discard-ignored` consente descartar gitignored no teardown. Substitui o antigo `worktree finish` + `archive` separados.
47
48
  - `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
49
  - `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
50
  - `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.
51
+ - `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. **Toda feature nasce numa branch `morph/{f}`** (ramificada da default) mesmo sem `--worktree` — por isso o fechamento por `morph-spec finish` é idêntico nos dois fluxos.
52
+ - `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).
53
+ - `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
54
  - `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
55
  - `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
- - `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).
56
+ - `morph-spec worktree setup|link|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; `list` mostra fase/gate de cada worktree ativo). O fechamento saiu daqui — use `morph-spec finish` (acima).
54
57
  - `morph-spec handoff <feature>` — regenera o documento de retomada (`.morph/features/{f}/handoff.md`): comprimido, derivado, aponta para os artefatos em vez de copiá-los. Gerado automaticamente em `setup`/`create --worktree`/`advance`.
55
58
  - `morph-spec fleet` — visão da frota: toda feature ativa (raiz + worktrees) com fase, gates pendentes, tasks, portas, handoff e atividade. Use entre sessões para decidir onde atuar.
56
59
  - `morph-spec telemetry [--json] [--feature <f>] [--limit <n>]` — reconstrói o caminho de execução da sessão a partir de `.morph/logs/events.jsonl` (skills invocadas, personas despachadas + prompts, standards/base de conhecimento lidos, mix de tools, prompts do usuário). Capturado silenciosamente pelo hook `telemetry-log` em todo tool call e todo UserPromptSubmit.
@@ -70,9 +73,9 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
70
73
 
71
74
  ## Roteamento por tipo de trabalho
72
75
 
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.
76
+ 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
77
 
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.
78
+ 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 e rode `morph-spec finish <f> --pr|--merge` (arquiva dentro da branch, integra e desmonta o worktree num passo só). Visão agregada: `morph-spec fleet`. Detalhes em `.morph/framework/MORPH.md` §2.2.
76
79
 
77
80
  ## Personas (em `.claude/agents/`)
78
81
 
@@ -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
  |------|----------|-------|
@@ -112,9 +112,11 @@ Nem todo trabalho merece o pipeline completo. Cada feature nasce com um **workTy
112
112
 
113
113
  **Compat:** uma feature sem `workType` (criada antes do v8) é tratada como `feature`. Mudança de tipo mid-flight: `morph-spec retype <f> <tipo>` (desfaz só carimbos de política, preserva aprovações humanas; bloqueado após o Gate 3).
114
114
 
115
+ **Nascimento numa branch:** toda feature — **com ou sem `--worktree`** — nasce numa branch `morph/{f}` ramificada da default. O fluxo comum (sem worktree) desenvolve direto em `morph/{f}` na raiz; o fluxo worktree isola em `.worktrees/{f}`. Por construção o fechamento é simétrico: `morph-spec finish <f> --pr|--merge` (§2.2) fecha os dois do mesmo jeito. Features criadas **antes** deste modelo (sem branch `morph/{f}`) fecham por `morph-spec archive <f>` (arquivar-sem-integrar), não por `finish`.
116
+
115
117
  ### 2.2 Desenvolvimento paralelo por worktree (frota)
116
118
 
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:
119
+ 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
120
 
119
121
  - **Uma feature = um worktree = uma instância.** Nunca abra duas instâncias na mesma feature (o setup recusa com exit 3).
120
122
  - 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.
@@ -125,7 +127,7 @@ Features independentes podem ser desenvolvidas **em paralelo, cada uma no seu gi
125
127
  - **Handoff entre sessões:** `.morph/features/{f}/handoff.md` é o documento de retomada — comprimido, derivado e regenerável (`morph-spec handoff <f>`), ele **aponta** para os artefatos em vez de copiá-los. É regenerado automaticamente no `worktree setup`, `create --worktree` e `advance`; o hook de SessionStart aponta para ele. **Ao abrir uma sessão numa feature em andamento, leia o handoff antes de re-derivar contexto à mão.** Nunca o edite manualmente (é git-invisível e sobrescrito na próxima regeneração).
126
128
  - **Visão da frota:** `morph-spec fleet` agrega raiz + worktrees (fase, gates pendentes, tasks, portas, handoff, último commit, sujeira) — use entre sessões para decidir onde atuar. `morph-spec worktree list` continua sendo a visão enxuta por worktree.
127
129
  - **Ownership de arquivos:** não há lock em runtime, por design — a colisão de escrita se resolve **em plan-time** (`morph-spec dag` detecta overlap de `outputs` e recomenda `isolation: "worktree"`) e **por construção** (uma feature = um worktree = um escritor). Entre features, arquivos compartilhados convergem no merge do `finish` — conflito ali é o sinal honesto de escopo sobreposto, não um bug.
128
- - **Fechamento:** aprove o Gate 3 dentro do worktree, depois `morph-spec worktree finish <f>` (merge `--no-ff` da branch na raiz primária limpa; recusa se worktree ou raiz estiverem sujos; aborta em conflito; libera o bloco de portas) e por fim `morph-spec archive <f>` na raiz.
130
+ - **Fechamento:** aprove o Gate 3, depois **`morph-spec finish <f> --pr|--merge`** — o encerramento único e simétrico (mesmo comando nos dois fluxos, worktree e comum). Ele **arquiva** a feature commitando o move `features/{f} → archive/{f}` **dentro da branch `morph/{f}`** (o estado arquivado viaja na própria integração, não num commit à parte) e então integra: `--merge` faz o merge `--no-ff` **local** na branch default (sem push; recusa se worktree/raiz estiverem sujos; aborta em conflito) e `--pr` faz `push` de `morph/{f}` + `gh pr create` (sem merge local; exige `origin` + `gh` autenticado — as pré-condições rodam **antes** de arquivar, então um `--pr` recusado não deixa meio-estado). Em ambos o worktree é desmontado (PROVE-or-DECLINE) e o bloco de portas liberado; no `--pr` a branch é **preservada** (o PR aberto precisa dela) e a sessão permanece em `morph/{f}`. Não há mais `worktree finish` nem `archive` separado no caminho feliz — `morph-spec archive` fica reservado a arquivar-sem-integrar (features legadas, faxina).
129
131
 
130
132
  ---
131
133
 
@@ -6,12 +6,18 @@ allowed-tools: Read, Bash
6
6
 
7
7
  # Archive MORPH Feature
8
8
 
9
- Archive a feature that has cleared Gate 3 (review). Use when the work is shipped and you want the
10
- feature out of the active set in `.morph/features/`.
9
+ Archive-**without-integrate**: move a feature out of the active set in `.morph/features/` without
10
+ merging or opening a PR. This is the **reserved** path — for **legacy** features (born before the
11
+ "every feature is born on a `morph/{f}` branch" model, so there's no branch to integrate) and for
12
+ **cleanup**. The **normal close** of a feature is `morph-spec finish {feature} --pr|--merge`, which
13
+ archives *and* integrates *and* tears down the worktree in one step (see `MORPH.md` §2.2 and
14
+ `morph-apply` §6). Reach for `/morph-archive` only when integration is not wanted.
11
15
 
12
16
  This is a thin wrapper over the atomic, git-aware CLI command — do **not** move folders or edit state
13
17
  by hand. The command moves the folder, stamps the feature `completed` + `archivedAt` in its
14
- `feature.json`, stages the move in git, and cleans the derivable root index, all-or-nothing.
18
+ `feature.json`, **commits the move scoped to the feature's own pathspec** (`.morph/features/{f}` +
19
+ `.morph/archive/{f}`, never `commit -a`), and cleans the derivable root index, all-or-nothing. Re-running
20
+ is idempotent: an already-archived feature is reported, not re-moved.
15
21
 
16
22
  ## Workflow
17
23
 
@@ -28,7 +34,8 @@ by hand. The command moves the folder, stamps the feature `completed` + `archive
28
34
  ```
29
35
  The command refuses if Gate 3 isn't approved (override with `--force` only when the user asks).
30
36
  It moves `.morph/features/{feature}/` → `.morph/archive/{feature}/` (via `git mv` when possible),
31
- sets `status: "completed"` + `archivedAt`, and drops the feature from the thin index.
37
+ sets `status: "completed"` + `archivedAt`, commits that move scoped to the feature's pathspec, and
38
+ drops the feature from the thin index.
32
39
 
33
40
  3. **Report** the new path (`.morph/archive/{feature}/`). Do not invent metrics not recorded in
34
41
  `feature.json` or `recap.md`.
@@ -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: {
@@ -1,14 +1,22 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "title": "MORPH-SPEC feature.json",
4
- "description": "Canonical shape of the authoritative, committed per-feature state at .morph/features/{feature}/feature.json (v5 per-feature state model). Born via `morph-spec create` (explicit) or the state-sync hook (implicit fallback). Server-side writers: `morph-spec approve/advance` (approvalGates), `morph-spec score` (taskScores), `morph-spec gate-decision` (gateDecisions), `morph-spec archive` (status/archivedAt). Never hand-edit the CLI-owned fields — manual Edits collide with the state-sync hook. This schema is documentational: additionalProperties stays true so older/newer CLIs interoperate.",
4
+ "description": "Canonical shape of the authoritative, committed per-feature state at .morph/features/{feature}/feature.json (v5 per-feature state model). Born via `morph-spec create` (explicit) or the state-sync hook (implicit fallback). Server-side writers: `morph-spec approve/advance` (approvalGates), `morph-spec score` (taskScores), `morph-spec gate-decision` (gateDecisions), `morph-spec archive`/`finish` (status/archivedAt — `finish` archives internally before integrating). Never hand-edit the CLI-owned fields — manual Edits collide with the state-sync hook. This schema is documentational: additionalProperties stays true so older/newer CLIs interoperate.",
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\").",
@@ -187,7 +200,7 @@
187
200
  },
188
201
  "worktree": {
189
202
  "type": "object",
190
- "description": "Bookkeeping do worktree ativo desta feature no ROOT feature.json, gravado por `morph-spec worktree setup` (via `create --worktree` ou standalone). Removido por `worktree finish`.",
203
+ "description": "Bookkeeping do worktree ativo desta feature no ROOT feature.json, gravado por `morph-spec worktree setup` (via `create --worktree` ou standalone). Removido no fechamento por `morph-spec finish` (que absorveu o antigo `worktree finish`).",
191
204
  "properties": {
192
205
  "path": { "type": "string" },
193
206
  "branch": { "type": "string" },