@polymorphism-tech/morph-spec 4.8.12 → 4.8.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +379 -379
- package/bin/{task-manager.cjs → task-manager.js} +47 -158
- package/claude-plugin.json +14 -14
- package/docs/CHEATSHEET.md +203 -203
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +111 -24
- package/framework/hooks/README.md +202 -202
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +6 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +7 -0
- package/framework/hooks/claude-code/statusline.py +6 -0
- package/framework/hooks/claude-code/stop/validate-completion.js +21 -2
- package/framework/hooks/shared/phase-utils.js +3 -0
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +55 -0
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +57 -1
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +23 -1
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +25 -2
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +1 -1
- package/package.json +87 -87
- package/src/commands/state/advance-phase.js +32 -13
- package/src/commands/tasks/task.js +2 -2
- package/src/core/paths/output-schema.js +1 -0
- package/src/lib/detectors/design-system-detector.js +5 -4
- package/src/lib/tasks/task-parser.js +94 -0
- package/src/lib/validators/content/content-validator.js +34 -106
|
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 2 (Design). Analyzes codebase/schema, then produce
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
7
|
-
cliVersion: "4.8.
|
|
7
|
+
cliVersion: "4.8.14"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH Design - FASE 2
|
|
@@ -58,6 +58,62 @@ Expanda a proposta em especificação técnica completa, contracts, decisões ar
|
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
|
+
## ✅ PRÉ-VOO OBRIGATÓRIO (antes de gerar qualquer artefato)
|
|
62
|
+
|
|
63
|
+
### 1. Verificar contexto injetado pelo SessionStart
|
|
64
|
+
|
|
65
|
+
O hook já injetou o estado atual. Confirme que você leu:
|
|
66
|
+
- Feature ativa, fase, status de approvals
|
|
67
|
+
- Task progress e pending gates
|
|
68
|
+
- Spec snippet (se disponível)
|
|
69
|
+
|
|
70
|
+
### 2. Ler todos os prerequisitos em PARALELO
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
# Uma única chamada, não sequencial:
|
|
74
|
+
Read: .morph/features/{feature}/0-proposal/proposal.md
|
|
75
|
+
+ Read: .morph/config/config.json (→ architecture.style)
|
|
76
|
+
+ Read: .morph/context/README.md (→ project context)
|
|
77
|
+
+ Read: framework/agents.json (→ activeAgents do feature)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Avaliar escopo → EnterPlanMode se necessário
|
|
81
|
+
|
|
82
|
+
Se QUALQUER uma das condições abaixo for verdadeira:
|
|
83
|
+
- [ ] Refactor toca ≥5 arquivos existentes de domínio
|
|
84
|
+
- [ ] Estimativa de tasks ≥20
|
|
85
|
+
- [ ] Mudança de arquitetura (ex: migrar de DDD Level 1 → Level 2)
|
|
86
|
+
|
|
87
|
+
→ **USE EnterPlanMode** antes de gerar contratos. Explore o codebase, apresente opções, aguarde aprovação.
|
|
88
|
+
|
|
89
|
+
### 4. Dispatch paralelo de subagents para artefatos independentes
|
|
90
|
+
|
|
91
|
+
Os 4 artefatos de design são **independentes entre si**. Dispatch em paralelo:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
# Executar TODOS ao mesmo tempo (uma mensagem, múltiplos Task tool calls):
|
|
95
|
+
Task(subagent=general): gerar spec.md (endpoints, regras de negócio)
|
|
96
|
+
Task(subagent=general): gerar contracts-vsa.cs ou contracts-levelN.cs
|
|
97
|
+
Task(subagent=general): gerar decisions.md (ADRs)
|
|
98
|
+
Task(subagent=general): gerar tasks.md skeleton
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Ou obter config de dispatch:
|
|
102
|
+
```bash
|
|
103
|
+
npx morph-spec dispatch-agents {feature} design --table
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 5. Criar tasks de sessão para visibilidade
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
TaskCreate: "Gerar spec.md" → activeForm: "Gerando spec.md"
|
|
110
|
+
TaskCreate: "Gerar contracts.cs" → activeForm: "Gerando contracts.cs"
|
|
111
|
+
TaskCreate: "Gerar decisions.md" → activeForm: "Gerando decisions.md"
|
|
112
|
+
TaskCreate: "Avanço de fase" → activeForm: "Avançando fase"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
61
117
|
## Workflow
|
|
62
118
|
|
|
63
119
|
### Passo 1: Carregar Contexto, Agentes e Dispatch Config
|
|
@@ -6,7 +6,7 @@ disable-model-invocation: true
|
|
|
6
6
|
context: fork
|
|
7
7
|
agent: general-purpose
|
|
8
8
|
user-invocable: false
|
|
9
|
-
cliVersion: "4.8.
|
|
9
|
+
cliVersion: "4.8.14"
|
|
10
10
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -65,6 +65,28 @@ Implemente as tasks definidas na FASE 4, com checkpoints a cada 3 tasks e recap
|
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
68
|
+
## ✅ PRÉ-VOO OBRIGATÓRIO (antes de iniciar implementação)
|
|
69
|
+
|
|
70
|
+
### 1. Ler contexto completo em PARALELO
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
# Uma única chamada, não sequencial:
|
|
74
|
+
Read: .morph/features/{feature}/3-tasks/tasks.md
|
|
75
|
+
+ Read: .morph/features/{feature}/1-design/spec.md
|
|
76
|
+
+ Read: .morph/features/{feature}/1-design/contracts.cs
|
|
77
|
+
+ Read: .morph/config/config.json (→ architecture.style)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2. Criar tasks de sessão por grupo de implementação
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
TaskCreate: "Implementar [domínio] (T001-T00N)" → activeForm: "Implementando [domínio]"
|
|
84
|
+
TaskCreate: "Checkpoints e validação" → activeForm: "Validando checkpoint"
|
|
85
|
+
TaskCreate: "Gerar recap.md" → activeForm: "Gerando recap"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
68
90
|
## Workflow
|
|
69
91
|
|
|
70
92
|
### Passo 0.5: Planejar Paralelização
|
|
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 1 (Setup). Reads project context, detects tech sta
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
7
|
-
cliVersion: "4.8.
|
|
7
|
+
cliVersion: "4.8.14"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH Setup - FASE 1
|
|
@@ -5,7 +5,7 @@ argument-hint: "[feature-name]"
|
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
user-invocable: false
|
|
7
7
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
8
|
-
cliVersion: "4.8.
|
|
8
|
+
cliVersion: "4.8.14"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# MORPH Tasks - FASE 4
|
|
@@ -50,6 +50,29 @@ Quebre a especificação em tasks executáveis, defina ordem de execução e est
|
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
53
|
+
## ✅ PRÉ-VOO OBRIGATÓRIO (antes de iniciar breakdown de tasks)
|
|
54
|
+
|
|
55
|
+
### 1. Ler todos os prerequisitos em PARALELO
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
# Uma única chamada, não sequencial:
|
|
59
|
+
Read: .morph/features/{feature}/1-design/spec.md
|
|
60
|
+
+ Read: .morph/features/{feature}/1-design/contracts.cs
|
|
61
|
+
+ Read: .morph/features/{feature}/1-design/decisions.md
|
|
62
|
+
+ Read: .morph/features/{feature}/1-design/schema-analysis.md (se existir)
|
|
63
|
+
+ Read: .morph/config/config.json (→ architecture.style)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Criar tasks de sessão para visibilidade
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
TaskCreate: "Analisar spec e definir tasks" → activeForm: "Analisando spec"
|
|
70
|
+
TaskCreate: "Gerar tasks.md" → activeForm: "Gerando tasks.md"
|
|
71
|
+
TaskCreate: "Avanço de fase" → activeForm: "Avançando fase"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
53
76
|
## Workflow
|
|
54
77
|
|
|
55
78
|
### CHECKPOINT DE ENTRADA: Verificar Pré-requisitos
|
|
@@ -329,7 +352,7 @@ Para cada task, estime tempo em minutos:
|
|
|
329
352
|
| Média (Business logic, validações) | 60-120 min |
|
|
330
353
|
| Complexa (Integrações, AI) | 120-240 min |
|
|
331
354
|
|
|
332
|
-
### Passo 6: Gerar `tasks.
|
|
355
|
+
### Passo 6: Gerar `tasks.md`
|
|
333
356
|
|
|
334
357
|
Crie `.morph/features/$ARGUMENTS/3-tasks/tasks.md` com a estrutura completa de tasks, checkpoints e estimativas.
|
|
335
358
|
|
|
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 1.5 (UI/UX). Creates design-system.md, mockups.md,
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
7
|
-
cliVersion: "4.8.
|
|
7
|
+
cliVersion: "4.8.14"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH UI/UX Design - FASE 1.5
|
package/package.json
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@polymorphism-tech/morph-spec",
|
|
3
|
-
"version": "4.8.
|
|
4
|
-
"description": "MORPH-SPEC: AI-First development framework with validation pipeline and multi-stack support",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"claude-code",
|
|
7
|
-
"claude",
|
|
8
|
-
"ai-coding",
|
|
9
|
-
"ai-first",
|
|
10
|
-
"spec-driven",
|
|
11
|
-
"dotnet",
|
|
12
|
-
"dotnet10",
|
|
13
|
-
"blazor",
|
|
14
|
-
"agent-framework",
|
|
15
|
-
"fluent-ui",
|
|
16
|
-
"framework",
|
|
17
|
-
"developer-tools",
|
|
18
|
-
"morph",
|
|
19
|
-
"polymorphism",
|
|
20
|
-
"micro-saas"
|
|
21
|
-
],
|
|
22
|
-
"main": "src/index.js",
|
|
23
|
-
"bin": {
|
|
24
|
-
"morph-spec": "bin/morph-spec.js"
|
|
25
|
-
},
|
|
26
|
-
"files": [
|
|
27
|
-
"bin/",
|
|
28
|
-
"src/",
|
|
29
|
-
"framework/",
|
|
30
|
-
"claude-plugin.json",
|
|
31
|
-
"docs/QUICKSTART.md",
|
|
32
|
-
"docs/CHEATSHEET.md",
|
|
33
|
-
"README.md",
|
|
34
|
-
"LICENSE",
|
|
35
|
-
"CLAUDE.md"
|
|
36
|
-
],
|
|
37
|
-
"type": "module",
|
|
38
|
-
"engines": {
|
|
39
|
-
"node": ">=18.0.0"
|
|
40
|
-
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"start": "node bin/morph-spec.js",
|
|
43
|
-
"generate": "node scripts/generate-refs.js",
|
|
44
|
-
"test": "node --test --test-concurrency=1",
|
|
45
|
-
"test:coverage": "c8 --reporter=text --reporter=html --reporter=lcov node --test --test-concurrency=1",
|
|
46
|
-
"test:coverage:summary": "c8 --reporter=text-summary node --test --test-concurrency=1",
|
|
47
|
-
"docs": "jsdoc -c jsdoc.json",
|
|
48
|
-
"docs:watch": "jsdoc -c jsdoc.json --watch",
|
|
49
|
-
"docs:serve": "npx http-server docs/api -p 8080 -o",
|
|
50
|
-
"version:bump": "node scripts/bump-version.js",
|
|
51
|
-
"release": "node scripts/release.js",
|
|
52
|
-
"postinstall": "node src/scripts/global-install.js"
|
|
53
|
-
},
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"ajv": "^8.12.0",
|
|
56
|
-
"ajv-formats": "^3.0.1",
|
|
57
|
-
"chalk": "^5.3.0",
|
|
58
|
-
"commander": "^12.0.0",
|
|
59
|
-
"diff": "^5.2.0",
|
|
60
|
-
"fs-extra": "^11.2.0",
|
|
61
|
-
"glob": "^10.3.0",
|
|
62
|
-
"handlebars": "^4.7.8",
|
|
63
|
-
"inquirer": "^9.2.0",
|
|
64
|
-
"minimatch": "^9.0.5",
|
|
65
|
-
"ora": "^8.0.0",
|
|
66
|
-
"yaml": "^2.3.4"
|
|
67
|
-
},
|
|
68
|
-
"repository": {
|
|
69
|
-
"type": "git",
|
|
70
|
-
"url": "git+https://github.com/lucasPolymorphism/morph-spec-framework.git"
|
|
71
|
-
},
|
|
72
|
-
"author": "Polymorphism Tech <contact@polymorphism.com.br>",
|
|
73
|
-
"license": "SEE LICENSE IN LICENSE",
|
|
74
|
-
"homepage": "https://polymorphism.com.br/morph-spec",
|
|
75
|
-
"bugs": {
|
|
76
|
-
"email": "support@polymorphism.com.br"
|
|
77
|
-
},
|
|
78
|
-
"private": false,
|
|
79
|
-
"publishConfig": {
|
|
80
|
-
"access": "public"
|
|
81
|
-
},
|
|
82
|
-
"devDependencies": {
|
|
83
|
-
"c8": "^10.1.3",
|
|
84
|
-
"docdash": "^2.0.2",
|
|
85
|
-
"jsdoc": "^4.0.5"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@polymorphism-tech/morph-spec",
|
|
3
|
+
"version": "4.8.14",
|
|
4
|
+
"description": "MORPH-SPEC: AI-First development framework with validation pipeline and multi-stack support",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude-code",
|
|
7
|
+
"claude",
|
|
8
|
+
"ai-coding",
|
|
9
|
+
"ai-first",
|
|
10
|
+
"spec-driven",
|
|
11
|
+
"dotnet",
|
|
12
|
+
"dotnet10",
|
|
13
|
+
"blazor",
|
|
14
|
+
"agent-framework",
|
|
15
|
+
"fluent-ui",
|
|
16
|
+
"framework",
|
|
17
|
+
"developer-tools",
|
|
18
|
+
"morph",
|
|
19
|
+
"polymorphism",
|
|
20
|
+
"micro-saas"
|
|
21
|
+
],
|
|
22
|
+
"main": "src/index.js",
|
|
23
|
+
"bin": {
|
|
24
|
+
"morph-spec": "bin/morph-spec.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin/",
|
|
28
|
+
"src/",
|
|
29
|
+
"framework/",
|
|
30
|
+
"claude-plugin.json",
|
|
31
|
+
"docs/QUICKSTART.md",
|
|
32
|
+
"docs/CHEATSHEET.md",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"CLAUDE.md"
|
|
36
|
+
],
|
|
37
|
+
"type": "module",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"start": "node bin/morph-spec.js",
|
|
43
|
+
"generate": "node scripts/generate-refs.js",
|
|
44
|
+
"test": "node --test --test-concurrency=1",
|
|
45
|
+
"test:coverage": "c8 --reporter=text --reporter=html --reporter=lcov node --test --test-concurrency=1",
|
|
46
|
+
"test:coverage:summary": "c8 --reporter=text-summary node --test --test-concurrency=1",
|
|
47
|
+
"docs": "jsdoc -c jsdoc.json",
|
|
48
|
+
"docs:watch": "jsdoc -c jsdoc.json --watch",
|
|
49
|
+
"docs:serve": "npx http-server docs/api -p 8080 -o",
|
|
50
|
+
"version:bump": "node scripts/bump-version.js",
|
|
51
|
+
"release": "node scripts/release.js",
|
|
52
|
+
"postinstall": "node src/scripts/global-install.js"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"ajv": "^8.12.0",
|
|
56
|
+
"ajv-formats": "^3.0.1",
|
|
57
|
+
"chalk": "^5.3.0",
|
|
58
|
+
"commander": "^12.0.0",
|
|
59
|
+
"diff": "^5.2.0",
|
|
60
|
+
"fs-extra": "^11.2.0",
|
|
61
|
+
"glob": "^10.3.0",
|
|
62
|
+
"handlebars": "^4.7.8",
|
|
63
|
+
"inquirer": "^9.2.0",
|
|
64
|
+
"minimatch": "^9.0.5",
|
|
65
|
+
"ora": "^8.0.0",
|
|
66
|
+
"yaml": "^2.3.4"
|
|
67
|
+
},
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/lucasPolymorphism/morph-spec-framework.git"
|
|
71
|
+
},
|
|
72
|
+
"author": "Polymorphism Tech <contact@polymorphism.com.br>",
|
|
73
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
74
|
+
"homepage": "https://polymorphism.com.br/morph-spec",
|
|
75
|
+
"bugs": {
|
|
76
|
+
"email": "support@polymorphism.com.br"
|
|
77
|
+
},
|
|
78
|
+
"private": false,
|
|
79
|
+
"publishConfig": {
|
|
80
|
+
"access": "public"
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"c8": "^10.1.3",
|
|
84
|
+
"docdash": "^2.0.2",
|
|
85
|
+
"jsdoc": "^4.0.5"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import chalk from 'chalk';
|
|
13
|
-
import { loadState, saveState, getFeature, getApprovalGate } from '../../core/state/state-manager.js';
|
|
13
|
+
import { loadState, saveState, getFeature, getApprovalGate, derivePhase } from '../../core/state/state-manager.js';
|
|
14
14
|
import { PHASES, validatePhase } from './validate-phase.js';
|
|
15
15
|
import { detectDesignSystem, hasUIAgentsActive } from '../../lib/detectors/design-system-detector.js';
|
|
16
16
|
import { getOutputPath } from '../../core/paths/output-schema.js';
|
|
@@ -29,7 +29,8 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
29
29
|
const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Get the next phase after the current one
|
|
32
|
+
* Get the next phase after the current one.
|
|
33
|
+
* Returns { nextPhase, skippedPhases } so callers can persist skipped phases in state.
|
|
33
34
|
*/
|
|
34
35
|
function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
35
36
|
// Load workflow config if workflow is set
|
|
@@ -40,9 +41,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
40
41
|
|
|
41
42
|
const currentIndex = PHASE_ORDER.indexOf(currentPhase);
|
|
42
43
|
if (currentIndex === -1 || currentIndex >= PHASE_ORDER.length - 1) {
|
|
43
|
-
return null;
|
|
44
|
+
return { nextPhase: null, skippedPhases: [] };
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
// Agents that justify the uiux phase (wireframes, design system).
|
|
48
|
+
// Implementation-only agents (blazor-builder, nextjs-expert) are NOT included —
|
|
49
|
+
// they build components from existing designs but don't create the design artifacts.
|
|
50
|
+
const uiDesignAgents = ['uiux-designer', 'ui-ux-designer', 'ui-designer'];
|
|
51
|
+
|
|
52
|
+
const skippedPhases = [];
|
|
53
|
+
|
|
46
54
|
for (let i = currentIndex + 1; i < PHASE_ORDER.length; i++) {
|
|
47
55
|
const candidate = PHASE_ORDER[i];
|
|
48
56
|
const phaseDef = PHASES[candidate];
|
|
@@ -52,6 +60,7 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
52
60
|
// Check if workflow explicitly skips this phase
|
|
53
61
|
if (workflowConfig.phases.skip && workflowConfig.phases.skip.includes(candidate)) {
|
|
54
62
|
console.log(chalk.gray(` ⏩ Skipping ${candidate}: workflow ${feature.workflow} skips this phase`));
|
|
63
|
+
skippedPhases.push(candidate);
|
|
55
64
|
continue;
|
|
56
65
|
}
|
|
57
66
|
|
|
@@ -60,6 +69,7 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
60
69
|
for (const [combinedName, phases] of Object.entries(workflowConfig.phases.combined)) {
|
|
61
70
|
if (phases.includes(candidate) && candidate !== combinedName) {
|
|
62
71
|
console.log(chalk.gray(` ⏩ Skipping ${candidate}: combined into ${combinedName} phase`));
|
|
72
|
+
skippedPhases.push(candidate);
|
|
63
73
|
continue;
|
|
64
74
|
}
|
|
65
75
|
}
|
|
@@ -72,6 +82,7 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
72
82
|
Object.keys(workflowConfig.phases.combined).includes(candidate);
|
|
73
83
|
if (!isCombinedPhase) {
|
|
74
84
|
console.log(chalk.gray(` ⏩ Skipping ${candidate}: not in workflow run list`));
|
|
85
|
+
skippedPhases.push(candidate);
|
|
75
86
|
continue;
|
|
76
87
|
}
|
|
77
88
|
}
|
|
@@ -83,6 +94,7 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
83
94
|
// Evaluate condition (simple conditions for now)
|
|
84
95
|
if (condition === '!hasUIAgents' && !hasUIAgentsActive(process.cwd(), featureName)) {
|
|
85
96
|
console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI agents active`));
|
|
97
|
+
skippedPhases.push(candidate);
|
|
86
98
|
continue;
|
|
87
99
|
}
|
|
88
100
|
}
|
|
@@ -93,15 +105,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
93
105
|
if (!workflowConfig) {
|
|
94
106
|
// Skip optional phases if flag set or no relevant agents/outputs
|
|
95
107
|
if (phaseDef?.optional && skipOptional) {
|
|
108
|
+
skippedPhases.push(candidate);
|
|
96
109
|
continue;
|
|
97
110
|
}
|
|
98
111
|
|
|
99
|
-
// Skip uiux if no UI agents are active
|
|
112
|
+
// Skip uiux if no UI design agents are active
|
|
100
113
|
if (candidate === 'uiux') {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
const hasUiDesignAgent = feature.activeAgents?.some(a => uiDesignAgents.includes(a));
|
|
115
|
+
if (!hasUiDesignAgent && skipOptional !== false) {
|
|
116
|
+
console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI design agents active`));
|
|
117
|
+
skippedPhases.push(candidate);
|
|
105
118
|
continue;
|
|
106
119
|
}
|
|
107
120
|
}
|
|
@@ -111,15 +124,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
111
124
|
const isSimple = feature.workflow === 'fast-track';
|
|
112
125
|
if (isSimple && skipOptional !== false) {
|
|
113
126
|
console.log(chalk.gray(` ⏩ Skipping ${candidate}: fast-track workflow`));
|
|
127
|
+
skippedPhases.push(candidate);
|
|
114
128
|
continue;
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
131
|
}
|
|
118
132
|
|
|
119
|
-
return candidate;
|
|
133
|
+
return { nextPhase: candidate, skippedPhases };
|
|
120
134
|
}
|
|
121
135
|
|
|
122
|
-
return null;
|
|
136
|
+
return { nextPhase: null, skippedPhases };
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
/**
|
|
@@ -138,7 +152,8 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
138
152
|
process.exit(1);
|
|
139
153
|
}
|
|
140
154
|
|
|
141
|
-
const
|
|
155
|
+
const featureFolderPath = join(process.cwd(), '.morph', 'features', feature);
|
|
156
|
+
const currentPhase = featureData.phase || derivePhase(featureFolderPath);
|
|
142
157
|
const currentPhaseDef = PHASES[currentPhase];
|
|
143
158
|
|
|
144
159
|
console.log(chalk.gray('Feature:'), feature);
|
|
@@ -146,7 +161,7 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
146
161
|
console.log(chalk.gray('Workflow:'), featureData.workflow || 'auto');
|
|
147
162
|
|
|
148
163
|
// Determine next phase
|
|
149
|
-
const nextPhase = getNextPhase(currentPhase, featureData, options.skipOptional, feature);
|
|
164
|
+
const { nextPhase, skippedPhases } = getNextPhase(currentPhase, featureData, options.skipOptional, feature);
|
|
150
165
|
|
|
151
166
|
if (!nextPhase) {
|
|
152
167
|
console.log(chalk.green('\n✓ Feature is at the final phase!'));
|
|
@@ -277,7 +292,7 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
277
292
|
}
|
|
278
293
|
|
|
279
294
|
// === GATE 5: Tasks Content Validation ===
|
|
280
|
-
// Validate tasks.
|
|
295
|
+
// Validate tasks.md structure when advancing to implement
|
|
281
296
|
if (currentPhase === 'tasks' && nextPhase === 'implement') {
|
|
282
297
|
if (featureData.outputs?.tasks?.created) {
|
|
283
298
|
const tasksContentValidation = validateTasksContent(featureData.outputs.tasks.path);
|
|
@@ -326,6 +341,10 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
326
341
|
const state = loadState();
|
|
327
342
|
state.features[feature].phase = nextPhase;
|
|
328
343
|
state.features[feature].updatedAt = new Date().toISOString();
|
|
344
|
+
if (skippedPhases.length > 0) {
|
|
345
|
+
const existingSkipped = state.features[feature].skippedPhases || [];
|
|
346
|
+
state.features[feature].skippedPhases = [...new Set([...existingSkipped, ...skippedPhases])];
|
|
347
|
+
}
|
|
329
348
|
saveState(state);
|
|
330
349
|
|
|
331
350
|
// Run PhaseAdvanced agent-teams hook (non-blocking)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Task management commands
|
|
3
|
-
*
|
|
3
|
+
* Spawns bin/task-manager.js (ESM) as a subprocess.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { spawn } from 'child_process';
|
|
@@ -9,7 +9,7 @@ import { dirname, join } from 'path';
|
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const taskManagerPath = join(__dirname, '..', '..', '..', 'bin', 'task-manager.
|
|
12
|
+
const taskManagerPath = join(__dirname, '..', '..', '..', 'bin', 'task-manager.js');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Execute task-manager.js with given arguments
|
|
@@ -28,6 +28,7 @@ export const OUTPUT_SCHEMA = {
|
|
|
28
28
|
spec: { filename: 'spec.md', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
29
29
|
clarifications:{ filename: 'clarifications.md', phaseDir: '1-design', phase: 'clarify', protected: false },
|
|
30
30
|
contracts: { filename: 'contracts.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
31
|
+
contractsVsa: { filename: 'contracts-vsa.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
31
32
|
tasks: { filename: 'tasks.md', phaseDir: '3-tasks', phase: 'tasks', protected: true, approvalGate: 'tasks' },
|
|
32
33
|
uiDesignSystem:{ filename: 'design-system.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
33
34
|
uiMockups: { filename: 'mockups.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
@@ -136,12 +136,13 @@ export function hasUIAgentsActive(projectPath, featureName) {
|
|
|
136
136
|
return false;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// UI agents that require design system
|
|
139
|
+
// UI design agents that require a design system before implement phase.
|
|
140
|
+
// Implementation-only agents (blazor-builder, nextjs-expert, css-specialist)
|
|
141
|
+
// build from existing designs and do NOT trigger this gate.
|
|
140
142
|
const uiAgents = [
|
|
141
|
-
'blazor-builder',
|
|
142
143
|
'ui-designer',
|
|
143
|
-
'
|
|
144
|
-
'
|
|
144
|
+
'uiux-designer',
|
|
145
|
+
'ui-ux-designer',
|
|
145
146
|
];
|
|
146
147
|
|
|
147
148
|
return feature.activeAgents.some(agentId => uiAgents.includes(agentId));
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Parser — Shared utilities for reading and syncing task state.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from bin/task-manager.cjs so both the task-manager and any
|
|
5
|
+
* future consumers can share the same parsing logic without duplication.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFile } from 'fs/promises';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// parseTasksMd
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse tasks.md to extract task stubs for the v3 state format.
|
|
17
|
+
* Accepts headings in either format:
|
|
18
|
+
* ### T001 — Task title (em-dash / en-dash / hyphen)
|
|
19
|
+
* ### T001: Task title (colon)
|
|
20
|
+
*
|
|
21
|
+
* @param {string} featureName
|
|
22
|
+
* @param {string} [cwd] - Project root (defaults to process.cwd())
|
|
23
|
+
* @returns {Promise<Array<{id, title, status, dependencies, files, checkpoint}>>}
|
|
24
|
+
*/
|
|
25
|
+
export async function parseTasksMd(featureName, cwd = process.cwd()) {
|
|
26
|
+
const tasksPath = join(cwd, `.morph/features/${featureName}/3-tasks/tasks.md`);
|
|
27
|
+
let content = '';
|
|
28
|
+
try {
|
|
29
|
+
content = await readFile(tasksPath, 'utf-8');
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const tasks = [];
|
|
35
|
+
const headingRe = /^###\s+(T\d+)\s*[—–:\-]\s*(.+)$/gm;
|
|
36
|
+
let match;
|
|
37
|
+
while ((match = headingRe.exec(content)) !== null) {
|
|
38
|
+
tasks.push({
|
|
39
|
+
id: match[1],
|
|
40
|
+
title: match[2].trim(),
|
|
41
|
+
status: 'pending',
|
|
42
|
+
dependencies: [],
|
|
43
|
+
files: [],
|
|
44
|
+
checkpoint: null,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return tasks;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// ensureTaskList
|
|
52
|
+
// ============================================================================
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ensure feature.taskList exists (array of individual task objects).
|
|
56
|
+
*
|
|
57
|
+
* In v3 state, feature.tasks is a counter object {total, completed, …}.
|
|
58
|
+
* Individual task objects live in feature.taskList.
|
|
59
|
+
*
|
|
60
|
+
* @param {Object} feature - Feature state object (mutated in place)
|
|
61
|
+
* @param {string} featureName
|
|
62
|
+
* @param {string} [cwd]
|
|
63
|
+
* @returns {Promise<Array>} The task list
|
|
64
|
+
*/
|
|
65
|
+
export async function ensureTaskList(feature, featureName, cwd = process.cwd()) {
|
|
66
|
+
if (Array.isArray(feature.tasks)) {
|
|
67
|
+
// v2 format: tasks IS the array
|
|
68
|
+
return feature.tasks;
|
|
69
|
+
}
|
|
70
|
+
// v3 format: use taskList or build from tasks.md
|
|
71
|
+
if (!feature.taskList || feature.taskList.length === 0) {
|
|
72
|
+
feature.taskList = await parseTasksMd(featureName, cwd);
|
|
73
|
+
}
|
|
74
|
+
return feature.taskList;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// syncCounters
|
|
79
|
+
// ============================================================================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* After modifying taskList, sync the counter fields back to feature.tasks.
|
|
83
|
+
* tasks.total is derived from the actual taskList length (not trusted from state).
|
|
84
|
+
*
|
|
85
|
+
* @param {Object} feature - Feature state object (mutated in place)
|
|
86
|
+
*/
|
|
87
|
+
export function syncCounters(feature) {
|
|
88
|
+
if (Array.isArray(feature.tasks)) return; // v2 format — nothing to sync
|
|
89
|
+
const list = feature.taskList || [];
|
|
90
|
+
feature.tasks.total = list.length;
|
|
91
|
+
feature.tasks.completed = list.filter(t => t.status === 'completed').length;
|
|
92
|
+
feature.tasks.inProgress = list.filter(t => t.status === 'in_progress').length;
|
|
93
|
+
feature.tasks.pending = list.filter(t => t.status === 'pending').length;
|
|
94
|
+
}
|