@polymorphism-tech/morph-spec 7.6.0 → 7.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/morph-spec.js +18 -0
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +12 -4
- package/framework/MORPH.md +18 -10
- package/framework/agents.json +12 -4
- package/framework/hooks/README.md +8 -10
- package/framework/hooks/claude-code/core/gate-guard.js +36 -16
- package/framework/hooks/claude-code/core/protect-spec-files.js +20 -5
- package/framework/hooks/claude-code/core/tasks-json-guard.js +81 -0
- package/framework/hooks/shared/tasks-validator.js +68 -0
- package/framework/mcp/registry.json +5 -48
- package/framework/rubrics/backend.json +3 -3
- package/framework/rubrics/default.json +3 -3
- package/framework/rubrics/frontend.json +3 -3
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +53 -40
- package/framework/skills/core/morph-brainstorming/SKILL.md +7 -12
- package/framework/skills/core/morph-eval/SKILL.md +10 -8
- package/framework/skills/core/morph-implement/SKILL.md +27 -21
- package/framework/skills/core/morph-plan/SKILL.md +12 -21
- package/framework/skills/core/morph-proposal/SKILL.md +3 -8
- package/framework/skills/core/morph-review/SKILL.md +30 -14
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/integration/mcp/mcp-tools.md +38 -43
- package/package.json +1 -1
- package/src/commands/project/doctor.js +36 -1
- package/src/commands/state/approve.js +29 -4
- package/src/commands/state/score.js +88 -0
- package/src/commands/tasks/dag.js +68 -0
- package/src/dashboard/scanner/rubrics-scanner.js +1 -1
- package/src/lib/generators/recap-generator.js +1 -1
- package/src/lib/tasks/dispatch-planner.js +201 -0
- package/src/lib/validators/score-aggregator.js +3 -3
- package/src/scripts/setup-infra.js +42 -0
- package/src/utils/agents-installer.js +142 -10
- package/src/utils/hooks-installer.js +13 -5
- package/framework/rubrics/eval-rubric.json +0 -66
|
@@ -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: "7.
|
|
7
|
+
cliVersion: "7.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -42,7 +42,7 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
42
42
|
|
|
43
43
|
4. Produza `tasks.json` em `.morph/features/{feature}/2-plan/` seguindo o schema em `.morph/framework/schemas/tasks.schema.json`.
|
|
44
44
|
|
|
45
|
-
**Formato obrigatório** (
|
|
45
|
+
**Formato obrigatório** (schema em `.morph/framework/schemas/tasks.schema.json` — o hook `tasks-json-guard` valida cada write):
|
|
46
46
|
|
|
47
47
|
```json
|
|
48
48
|
{
|
|
@@ -72,27 +72,17 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
72
72
|
- `group` (opcional): backend, frontend, tests, infra, docs. Default backend.
|
|
73
73
|
- `outputs` (opcional): array de paths que serão criados/modificados.
|
|
74
74
|
|
|
75
|
-
Antes de retornar,
|
|
76
|
-
```js
|
|
77
|
-
import { validateTasksJson, writeTasksJson } from '../../../src/lib/tasks/tasks-json-parser.js';
|
|
78
|
-
const { valid, errors } = validateTasksJson(data);
|
|
79
|
-
if (!valid) throw new Error(errors.join('\n'));
|
|
80
|
-
await writeTasksJson(tasksPath, data);
|
|
81
|
-
```
|
|
75
|
+
Antes de retornar, valide rodando `morph-spec dag {feature}` — o comando parseia e valida o schema do tasks.json (ids únicos `T\d+`, enums de `status`/`effort`, `dependencies` válidas) e já imprime a análise de DAG que a `/morph-apply` vai usar. Erros de schema abortam com a lista do que corrigir.
|
|
82
76
|
|
|
83
77
|
5. Apresente o Gate 2 segundo trust mode.
|
|
84
78
|
|
|
85
|
-
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular sinais de risco:
|
|
79
|
+
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular os sinais de risco em linguagem natural — sem scripts:
|
|
86
80
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
standardsViolations: /* detectado em mandate */,
|
|
93
|
-
};
|
|
94
|
-
const { pause, reason } = shouldPauseGate(config, signals);
|
|
95
|
-
```
|
|
81
|
+
- `taskCount` = número de tasks do tasks.json.
|
|
82
|
+
- `designSystemDivergence` = herdado do brainstorming.
|
|
83
|
+
- `standardsViolations` = violações detectadas no mandate.
|
|
84
|
+
|
|
85
|
+
**Decisão de pausa** (regras da tabela do MORPH.md §5): `trust: manual` → pausa sempre; `designSystemDivergence` → pausa; `standardsViolations` não-vazio → pausa; `taskCount > 20` → pausa; caso contrário → auto-passa (`pause: false`).
|
|
96
86
|
|
|
97
87
|
**Persista a decisão no `feature.json` autoritativo da feature ANTES de apresentar ou auto-aprovar** — grave
|
|
98
88
|
`gateDecisions.plan = { pause, reason, signals, trust, decidedAt: <ISO> }` em `.morph/features/{feature}/feature.json`
|
|
@@ -137,5 +127,6 @@ Ao iniciar a fase plan — após o `proposal.md` ser aprovado e antes de qualque
|
|
|
137
127
|
- `.morph/framework/schemas/tasks.schema.json` — schema obrigatório de tasks.json
|
|
138
128
|
- `.morph/features/{feature}/0-proposal/proposal.md` — escopo da feature
|
|
139
129
|
- `.morph/features/{feature}/0-proposal/stack-scan.md` — contexto técnico detectado
|
|
140
|
-
-
|
|
141
|
-
- `
|
|
130
|
+
- `.morph/framework/schemas/tasks.schema.json` — schema e enums de tasks.json
|
|
131
|
+
- `morph-spec dag {feature}` — valida o tasks.json e imprime a análise de DAG
|
|
132
|
+
- `.morph/framework/MORPH.md` §5 — regras de pausa de gate (trust mode)
|
|
@@ -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: "7.
|
|
7
|
+
cliVersion: "7.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-proposal
|
|
@@ -52,12 +52,7 @@ Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular sinais de
|
|
|
52
52
|
- `designSystemDivergence` — detectado pela morph-brainstorming.
|
|
53
53
|
- `standardsViolations` — violações de mandate listadas na proposal (não deveria, mas verifica).
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```js
|
|
58
|
-
import { shouldPauseGate } from '../../../src/lib/risk-detector.js';
|
|
59
|
-
const { pause, reason } = shouldPauseGate(config, signals);
|
|
60
|
-
```
|
|
55
|
+
**Decisão de pausa** (regras da tabela do MORPH.md §5, aplicadas em linguagem natural — sem scripts): `trust: manual` → `pause: true` sempre; `designSystemDivergence` → pausa; `standardsViolations` não-vazio → pausa; caso contrário → `pause: false` (auto-passa). Monte `reason` com o sinal que disparou.
|
|
61
56
|
|
|
62
57
|
**Se `pause === true`**, apresentar via `AskUserQuestion`:
|
|
63
58
|
|
|
@@ -136,4 +131,4 @@ Lógica idêntica ao passo 3 (`shouldPauseGate` + AskUserQuestion ou auto-aprova
|
|
|
136
131
|
- `.morph/framework/MORPH.md` — charter NLH completo
|
|
137
132
|
- `framework/skills/core/morph-brainstorming/SKILL.md`
|
|
138
133
|
- `framework/skills/core/morph-plan/SKILL.md`
|
|
139
|
-
-
|
|
134
|
+
- `.morph/framework/MORPH.md` §5 — regras de pausa de gate (trust mode)
|
|
@@ -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: "7.
|
|
7
|
+
cliVersion: "7.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -33,7 +33,27 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
33
33
|
- Se não atendido: registre como "bloqueante" com descrição do gap.
|
|
34
34
|
- Se parcialmente atendido: registre como "pendência" com o que falta.
|
|
35
35
|
|
|
36
|
-
4.
|
|
36
|
+
4. **Despache o avaliador independente** — o score do Gate 3 nunca é auto-atribuído por quem implementou:
|
|
37
|
+
- Rode a suíte de testes do projeto e capture a **saída real** (não um resumo seu).
|
|
38
|
+
- Gere o **diff da implementação** (`git diff` do range da feature, ou a lista de arquivos do recap.md com seus conteúdos atuais).
|
|
39
|
+
- Despache o subagent registrado `morph-evaluator` (gerado de `agents.json` — a persona do avaliador é o system prompt dele, com tools read-only), com contexto fresco:
|
|
40
|
+
```
|
|
41
|
+
dispatch Agent (subagent_type: "morph-evaluator") with prompt:
|
|
42
|
+
contexto: mandate.md + spec.md + diff da implementação + saída real dos testes
|
|
43
|
+
instrução: |
|
|
44
|
+
Você NÃO participou da implementação — avalie apenas o que está no diff,
|
|
45
|
+
contra o mandate. Aplique as rubricas em .morph/framework/evals/rubrics/
|
|
46
|
+
(architecture 30%, contracts 25%, code-quality 25%, test-coverage 20%)
|
|
47
|
+
seguindo as bandas 0-10 de cada uma.
|
|
48
|
+
NÃO reescreva código. NÃO toque em tasks.json, feature.json nem no board
|
|
49
|
+
de sessão. Ao concluir, ENTREGUE o relatório final — terminar idle sem
|
|
50
|
+
reportar é falha de protocolo.
|
|
51
|
+
Retorne: score 0-10 por dimensão, score composto (arch×0.30 + contracts×0.25
|
|
52
|
+
+ quality×0.25 + tests×0.20), e feedback priorizado (bloqueantes primeiro)
|
|
53
|
+
para qualquer dimensão < 9.
|
|
54
|
+
```
|
|
55
|
+
- **Fallback (tipo não registrado):** se `morph-evaluator` não estiver na sua lista de subagent types, despache `general-purpose` colando o campo `persona` do agente `evaluator` (Read em `.morph/framework/agents.json`) no topo do prompt — e **registre o fallback no review-report** (seção de método).
|
|
56
|
+
- Os scores por dimensão e o composto do review-report vêm **desse retorno**, não da sua auto-avaliação. Se o avaliador ficar idle sem reportar, cobre via SendMessage antes de prosseguir.
|
|
37
57
|
|
|
38
58
|
5. Produza `review-report.md` em `.morph/features/{feature}/4-review/` com:
|
|
39
59
|
- Scores por dimensão (arquitetura, contratos, qualidade, cobertura)
|
|
@@ -55,16 +75,12 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
55
75
|
|
|
56
76
|
8. Apresente o Gate 3 segundo trust mode.
|
|
57
77
|
|
|
58
|
-
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular sinais de risco:
|
|
78
|
+
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular os sinais de risco em linguagem natural — sem scripts:
|
|
59
79
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
standardsViolations: blockingItems.filter(b => b.kind === 'standard').map(b => b.id),
|
|
65
|
-
};
|
|
66
|
-
const { pause, reason } = shouldPauseGate(config, signals);
|
|
67
|
-
```
|
|
80
|
+
- `evalScore` = score composto retornado pelo **avaliador independente** (passo 4).
|
|
81
|
+
- `standardsViolations` = ids dos itens bloqueantes de standard encontrados no passo 3.
|
|
82
|
+
|
|
83
|
+
**Decisão de pausa** (mesmas regras da tabela do MORPH.md §5): `trust: manual` → `pause: true` sempre; `standardsViolations` não-vazio → `pause: true`; `evalScore < 9` → `pause: true`; caso contrário → `pause: false` (auto-passa).
|
|
68
84
|
|
|
69
85
|
**Se `pause === true`**, use `AskUserQuestion`:
|
|
70
86
|
|
|
@@ -96,11 +112,11 @@ e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `
|
|
|
96
112
|
- Gate 3 registrado aprovado em `.morph/features/{feature}/feature.json` (autoritativo), na forma canônica `{ approved, timestamp, approvedBy }` — via `morph-spec approve` em auto, ou pelo hook state-sync no caminho manual
|
|
97
113
|
|
|
98
114
|
## Referências
|
|
99
|
-
- `.morph/framework/MORPH.md` — seção 5 (Gate 3), seção 1 (papel Evaluator), Trust Mode
|
|
100
|
-
-
|
|
115
|
+
- `.morph/framework/MORPH.md` — seção 5 (Gate 3 + regras de pausa), seção 1 (papel Evaluator), Trust Mode
|
|
116
|
+
- `.claude/agents/morph-evaluator.md` — subagent do avaliador (gerado de `.morph/framework/agents.json`), despachado no passo 4
|
|
117
|
+
- `framework/skills/core/morph-eval/SKILL.md` — rubricas e bandas que o avaliador aplica
|
|
101
118
|
- `.morph/framework/evals/trace-schema.md` — formato canônico do trace
|
|
102
119
|
- `.morph/features/{feature}/2-plan/mandate.md` — critérios de done da feature
|
|
103
120
|
- `.morph/features/{feature}/2-plan/spec.md` — arquitetura e contratos
|
|
104
121
|
- `.morph/features/{feature}/2-plan/decisions.md` — ADRs registrados (se existir)
|
|
105
122
|
- `.morph/features/{feature}/3-implement/recap.md` — registro de execução
|
|
106
|
-
- `src/lib/risk-detector.js` — `shouldPauseGate` para Gate 3
|
|
@@ -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: "7.
|
|
7
|
+
cliVersion: "7.7.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: "7.
|
|
7
|
+
cliVersion: "7.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Keywords:** mcp, model-context-protocol, mcp-tools, context7, playwright, mcp-client
|
|
6
6
|
> **Load When:** an MCP tool is used in any morph-spec phase
|
|
7
7
|
|
|
8
|
-
**Verified against:** Claude Code MCP tooling — context7, Playwright
|
|
8
|
+
**Verified against:** Claude Code MCP tooling — context7, Playwright; GitHub via `gh` CLI; Neon via `neonctl`. Last-verified: 2026-07-08.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -19,8 +19,8 @@ Before using any MCP tool, check if it's available in the current session:
|
|
|
19
19
|
// Pattern: Attempt the call — if the tool doesn't exist, Claude Code will report it
|
|
20
20
|
// There is no "list MCPs" command; just try the most common operation
|
|
21
21
|
|
|
22
|
-
// GitHub MCP
|
|
23
|
-
|
|
22
|
+
// GitHub — o morph-spec NÃO usa MCP do GitHub: use o `gh` CLI via Bash
|
|
23
|
+
// gh repo view --json name,defaultBranchRef,languages
|
|
24
24
|
|
|
25
25
|
// Context7 (library docs)
|
|
26
26
|
await mcp__context7__resolve_library_id({ libraryName: "react" });
|
|
@@ -41,18 +41,19 @@ await mcp__playwright__browser_navigate({ url: "https://example.com" });
|
|
|
41
41
|
|
|
42
42
|
### Phase 1 (Setup) — Project Discovery
|
|
43
43
|
|
|
44
|
-
|
|
|
45
|
-
|
|
46
|
-
| **GitHub** | Repo info, recent PRs, issues | `
|
|
44
|
+
| Tool | Use Case | Example |
|
|
45
|
+
|------|----------|---------|
|
|
46
|
+
| **GitHub CLI (`gh`)** | Repo info, recent PRs, issues | `gh repo view --json ...` (Bash) |
|
|
47
47
|
| **Filesystem** | Scan project structure | Native Glob/Read preferred |
|
|
48
48
|
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
```bash
|
|
50
|
+
# Detect project type from repo metadata (gh CLI via Bash — sem MCP)
|
|
51
|
+
gh repo view --json name,defaultBranchRef,languages,repositoryTopics
|
|
52
|
+
gh pr list --limit 10 --json title,state,updatedAt
|
|
53
|
+
gh issue list --limit 10 --json title,labels
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
# Fallback: use Glob to detect stack
|
|
56
|
+
# Glob: "**/{package.json,*.csproj,*.sln,go.mod,Cargo.toml}"
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
### Phase 2 (UI/UX) — Design References
|
|
@@ -143,7 +144,7 @@ await mcp__gemini_image_mcp__continue_editing({
|
|
|
143
144
|
|------|----------|---------|
|
|
144
145
|
| **Neon CLI (`neonctl`) / psql** | Database schema, tables, relationships, RLS | `psql <DB_URL> -c "SELECT ..."` |
|
|
145
146
|
| **Context7** | Library docs for architecture decisions | `mcp__context7__query_docs()` |
|
|
146
|
-
| **GitHub** | Check existing code patterns, PRs | `
|
|
147
|
+
| **GitHub CLI (`gh`)** | Check existing code patterns, PRs | `gh search code` (Bash) |
|
|
147
148
|
|
|
148
149
|
```bash
|
|
149
150
|
# === SCHEMA ANALYSIS (Critical for contracts.cs) ===
|
|
@@ -183,7 +184,7 @@ psql $(neonctl connection-string) -c "SELECT tablename, policyname, cmd, qual FR
|
|
|
183
184
|
| MCP | Use Case | Example |
|
|
184
185
|
|-----|----------|---------|
|
|
185
186
|
| **Context7** | Verify library capabilities, API limits | `mcp__context7__query_docs()` |
|
|
186
|
-
| **GitHub** | Check issue discussions, known limitations | `
|
|
187
|
+
| **GitHub CLI (`gh`)** | Check issue discussions, known limitations | `gh search issues` (Bash) |
|
|
187
188
|
|
|
188
189
|
```javascript
|
|
189
190
|
// Verify if library supports a required feature
|
|
@@ -198,24 +199,17 @@ const docs = await mcp__context7__query_docs({
|
|
|
198
199
|
|
|
199
200
|
### Phase 6 (Tasks) — Planning & Organization
|
|
200
201
|
|
|
201
|
-
|
|
|
202
|
-
|
|
203
|
-
| **GitHub** | Create issues from tasks, link to milestone | `
|
|
202
|
+
| Tool | Use Case | Example |
|
|
203
|
+
|------|----------|---------|
|
|
204
|
+
| **GitHub CLI (`gh`)** | Create issues from tasks, link to milestone | `gh issue create` (Bash) |
|
|
204
205
|
| **Context7** | Estimate complexity based on library docs | `mcp__context7__query_docs()` |
|
|
205
206
|
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
labels: [task.category],
|
|
213
|
-
milestone: featureMilestone
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Fallback: Bash with gh CLI
|
|
218
|
-
// gh issue create --title "T001: Create Entity Lead" --label "domain"
|
|
207
|
+
```bash
|
|
208
|
+
# Create GitHub issues from tasks.json (if team uses GitHub Projects)
|
|
209
|
+
# — one `gh issue create` per task, iterating tasks.json:
|
|
210
|
+
gh issue create --title "T001: Create Entity Lead" \
|
|
211
|
+
--body "description + doneCriteria da task" \
|
|
212
|
+
--label "backend" --milestone "feature-x"
|
|
219
213
|
```
|
|
220
214
|
|
|
221
215
|
### Phase 7 (Implement) — Build & Deploy
|
|
@@ -223,7 +217,7 @@ for (const task of tasks) {
|
|
|
223
217
|
| Tool | Use Case | Example |
|
|
224
218
|
|------|----------|---------|
|
|
225
219
|
| **Neon CLI (`neonctl`) / psql** | Run migrations, create RLS policies | `dotnet ef database update` |
|
|
226
|
-
| **GitHub** | Create PR, push branches | `
|
|
220
|
+
| **GitHub CLI (`gh`)** | Create PR, push branches | `gh pr create` (Bash) |
|
|
227
221
|
| **Context7** | Look up API usage during coding | `mcp__context7__query_docs()` |
|
|
228
222
|
| **Playwright** | Smoke test deployed features, verify UI | `mcp__playwright__browser_navigate()` |
|
|
229
223
|
| **Vercel** | Deploy, check logs, manage env vars | `mcp__vercel__list_projects()` |
|
|
@@ -283,7 +277,7 @@ await mcp__vercel__manage_env_vars({
|
|
|
283
277
|
target: ['production', 'preview']
|
|
284
278
|
});
|
|
285
279
|
|
|
286
|
-
//
|
|
280
|
+
// PRs/issues: sempre `gh` CLI (Bash). Deployments fallback: vercel CLI
|
|
287
281
|
```
|
|
288
282
|
|
|
289
283
|
---
|
|
@@ -293,7 +287,7 @@ await mcp__vercel__manage_env_vars({
|
|
|
293
287
|
| Need | MCP Tool | Native Alternative |
|
|
294
288
|
|------|----------|--------------------|
|
|
295
289
|
| Database schema | Neon CLI (`neonctl`) / psql | Grep queries + Read types |
|
|
296
|
-
| Repo metadata
|
|
290
|
+
| Repo metadata / issues / PRs | Bash `gh` CLI | WebFetch `api.github.com` (read-only) |
|
|
297
291
|
| Library docs | Context7 | WebSearch + WebFetch |
|
|
298
292
|
| Live page preview | Playwright MCP | WebFetch URL |
|
|
299
293
|
| Page interaction (click, type, navigate) | Playwright MCP | Manual testing |
|
|
@@ -304,7 +298,7 @@ await mcp__vercel__manage_env_vars({
|
|
|
304
298
|
| Vercel deployments | Vercel MCP | Bash `vercel` CLI |
|
|
305
299
|
| AI image generation | Gemini Image MCP | Manual creation or stock photos |
|
|
306
300
|
|
|
307
|
-
**Rule:** MCP tools provide structured data (JSON responses). Native tools require manual parsing. **Always prefer MCP when available** — fall back to native when not. **Exceptions:** Neon, Docker, and
|
|
301
|
+
**Rule:** MCP tools provide structured data (JSON responses). Native tools require manual parsing. **Always prefer MCP when available** — fall back to native when not. **Exceptions:** Neon, Docker, Azure **and GitHub** always use their respective CLIs directly — never MCP (`gh` já retorna JSON estruturado via `--json`, sem Docker nem PAT em env var).
|
|
308
302
|
|
|
309
303
|
---
|
|
310
304
|
|
|
@@ -445,16 +439,17 @@ const docs = await mcp__context7__query_docs({
|
|
|
445
439
|
});
|
|
446
440
|
```
|
|
447
441
|
|
|
448
|
-
### GitHub: Code Search Across Repo
|
|
442
|
+
### GitHub: Code Search Across Repo (`gh` CLI — sem MCP)
|
|
449
443
|
|
|
450
|
-
|
|
444
|
+
O morph-spec **não instala nem usa MCP do GitHub**. O `gh` CLI (autenticado uma vez via `gh auth login`) cobre a mesma superfície com JSON estruturado, sem Docker e sem PAT em variável de ambiente:
|
|
451
445
|
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
446
|
+
```bash
|
|
447
|
+
# Find patterns in large codebase during Phase 2
|
|
448
|
+
gh search code "DbSet leads" --repo myorg/myrepo --json path,textMatches
|
|
449
|
+
# → Find all places that query the leads table
|
|
450
|
+
|
|
451
|
+
# Qualquer endpoint REST sem subcomando dedicado:
|
|
452
|
+
gh api repos/{owner}/{repo}/branches --jq '.[].name'
|
|
458
453
|
```
|
|
459
454
|
|
|
460
455
|
---
|
|
@@ -463,7 +458,7 @@ const results = await mcp__github__search_code({
|
|
|
463
458
|
|
|
464
459
|
- **Never pass secrets** in MCP tool parameters (API keys, tokens, passwords)
|
|
465
460
|
- **Neon** uses neonctl/psql — use database branches for development, not production
|
|
466
|
-
- **GitHub
|
|
461
|
+
- **GitHub via `gh` CLI** herda as permissões do login do `gh auth login` (keyring) — sem PAT em env var, sem Docker
|
|
467
462
|
- **Always validate tool responses** before using in contracts or code generation
|
|
468
463
|
|
|
469
464
|
---
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
getInstalledCLIVersion
|
|
13
13
|
} from '../../utils/version-checker.js';
|
|
14
14
|
import { resetMorphSettings } from '../../utils/claude-settings-manager.js';
|
|
15
|
-
import { checkMissingAgents } from '../../utils/agents-installer.js';
|
|
15
|
+
import { checkMissingAgents, checkAgentsDrift } from '../../utils/agents-installer.js';
|
|
16
16
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
17
17
|
|
|
18
18
|
const isWindows = platform() === 'win32';
|
|
@@ -186,6 +186,41 @@ async function doctorFullCommand(frameworkRoot) {
|
|
|
186
186
|
} catch { /* ignore parse errors — already caught above */ }
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
// ── 6b. Generated personas drift (.claude/agents ↔ agents.json) ─────────
|
|
190
|
+
// agents.json is the single source of truth; the .claude/agents/morph-*.md
|
|
191
|
+
// files are derived artifacts (ADR-001). Missing = broken dispatch (error);
|
|
192
|
+
// stale/hand-edited or orphan = drift (warning).
|
|
193
|
+
if (await pathExists(agentsPath)) {
|
|
194
|
+
console.log(chalk.cyan('\n .claude/agents/ — Generated personas (drift vs agents.json)'));
|
|
195
|
+
try {
|
|
196
|
+
let projectTags = [];
|
|
197
|
+
const cfgPath = join(frameworkRoot, '.morph/config/config.json');
|
|
198
|
+
if (await pathExists(cfgPath)) {
|
|
199
|
+
try { projectTags = getProjectTags(JSON.parse(await fs.readFile(cfgPath, 'utf8'))); } catch { /* no tags */ }
|
|
200
|
+
}
|
|
201
|
+
const drift = checkAgentsDrift(frameworkRoot, join(frameworkRoot, '.morph/framework'), projectTags);
|
|
202
|
+
check(`Personas installed (${drift.expected - drift.missing.length}/${drift.expected})`,
|
|
203
|
+
drift.missing.length === 0, false,
|
|
204
|
+
drift.missing.length > 0 ? `missing: ${drift.missing.join(', ')} — run 'morph-spec setup-infra --reinstall'` : '');
|
|
205
|
+
check('Personas up-to-date (no drift)', drift.stale.length === 0, true,
|
|
206
|
+
drift.stale.length > 0 ? `stale/hand-edited: ${drift.stale.join(', ')} — regenerate via 'morph-spec setup-infra --reinstall'` : '');
|
|
207
|
+
check('No orphan morph-* agents', drift.orphans.length === 0, true,
|
|
208
|
+
drift.orphans.length > 0 ? `orphans: ${drift.orphans.join(', ')}` : '');
|
|
209
|
+
} catch {
|
|
210
|
+
check('personas drift check', false, true, 'failed to compute');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── 6c. GitHub tooling — gh CLI (canonical; morph-spec does not use a GitHub MCP) ──
|
|
215
|
+
console.log(chalk.cyan('\n Tooling — GitHub via gh CLI'));
|
|
216
|
+
try {
|
|
217
|
+
execSync('gh --version', { stdio: 'pipe', timeout: 5000 });
|
|
218
|
+
check('gh CLI available', true);
|
|
219
|
+
} catch {
|
|
220
|
+
check('gh CLI available', false, true,
|
|
221
|
+
"GitHub ops (issues/PRs/releases) usam o gh CLI — instale https://cli.github.com e rode 'gh auth login'");
|
|
222
|
+
}
|
|
223
|
+
|
|
189
224
|
// ── 7. state.json schema version ────────────────────────────────────────
|
|
190
225
|
console.log(chalk.cyan('\n .morph/state.json — Schema Version'));
|
|
191
226
|
const statePath = join(frameworkRoot, '.morph/state.json');
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { getFeature, setApprovalGate, getApprovalGate, getApprovalHistory, derivePhase } from '../../core/state/state-manager.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Unapprove action function - Revoke a previously approved gate
|
|
@@ -158,12 +160,23 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
158
160
|
process.exit(1);
|
|
159
161
|
}
|
|
160
162
|
|
|
163
|
+
// v5 removed the stored feature.phase — derive it from the filesystem +
|
|
164
|
+
// approved gates (same as `morph-spec status`), never print "unknown".
|
|
165
|
+
const featurePath = join(process.cwd(), '.morph', 'features', featureName);
|
|
166
|
+
const phase = feature.phase || derivePhase(featurePath);
|
|
167
|
+
|
|
168
|
+
// The uiux branch is optional: a feature that never took it has no
|
|
169
|
+
// 1-design/ folder and no uiux approval — that is "n/a", not "rejected".
|
|
170
|
+
const uiuxGate = getApprovalGate(featureName, 'uiux');
|
|
171
|
+
const uiuxTaken = uiuxGate?.approved === true || existsSync(join(featurePath, '1-design'));
|
|
172
|
+
|
|
161
173
|
// JSON output
|
|
162
174
|
if (options.json) {
|
|
163
175
|
const status = {
|
|
164
176
|
feature: featureName,
|
|
165
|
-
phase
|
|
177
|
+
phase,
|
|
166
178
|
status: feature.status,
|
|
179
|
+
uiuxTaken,
|
|
167
180
|
gates: {}
|
|
168
181
|
};
|
|
169
182
|
|
|
@@ -179,7 +192,7 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
179
192
|
console.log('━'.repeat(60));
|
|
180
193
|
|
|
181
194
|
// Show current phase
|
|
182
|
-
console.log(chalk.cyan(`\nCurrent Phase: ${
|
|
195
|
+
console.log(chalk.cyan(`\nCurrent Phase: ${phase}`));
|
|
183
196
|
console.log(chalk.gray(`Feature Status: ${feature.status || 'unknown'}\n`));
|
|
184
197
|
|
|
185
198
|
// Show each gate
|
|
@@ -193,6 +206,11 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
193
206
|
continue;
|
|
194
207
|
}
|
|
195
208
|
|
|
209
|
+
if (gateName === 'uiux' && !uiuxTaken) {
|
|
210
|
+
console.log(` ${chalk.gray('—')} ${gateName}: ${chalk.gray('n/a (ramo de design não tomado)')}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
196
214
|
if (gateStatus.approved) {
|
|
197
215
|
console.log(` ${chalk.green('✓')} ${gateName}: ${chalk.green('Approved')}`);
|
|
198
216
|
if (gateStatus.timestamp) {
|
|
@@ -202,7 +220,14 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
202
220
|
console.log(chalk.gray(` Approved by: ${gateStatus.approvedBy}`));
|
|
203
221
|
}
|
|
204
222
|
} else {
|
|
205
|
-
|
|
223
|
+
// Distinguish "never evaluated" (initialized approved:false, no
|
|
224
|
+
// timestamp) from an explicit rejection/revocation (has timestamp).
|
|
225
|
+
const neverEvaluated = !gateStatus.timestamp && !gateStatus.rejectedAt && !gateStatus.revokedAt;
|
|
226
|
+
if (neverEvaluated) {
|
|
227
|
+
console.log(` ${chalk.gray('○')} ${gateName}: ${chalk.gray('Pending (not yet evaluated)')}`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
console.log(` ${chalk.red('✗')} ${gateName}: ${chalk.red('Rejected / Revoked')}`);
|
|
206
231
|
if (gateStatus.reason) {
|
|
207
232
|
console.log(chalk.gray(` Reason: ${gateStatus.reason}`));
|
|
208
233
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { getFeature, updateFeature } from '../../core/state/state-manager.js';
|
|
5
|
+
|
|
6
|
+
/** Fixed --dims order: a,c,q,t (mirrors the morph-eval dimension weights). */
|
|
7
|
+
const DIM_KEYS = ['architecture', 'contracts', 'codeQuality', 'testCoverage'];
|
|
8
|
+
|
|
9
|
+
function parseScoreValue(raw, label) {
|
|
10
|
+
const value = Number(raw);
|
|
11
|
+
if (!Number.isFinite(value) || value < 0 || value > 10) {
|
|
12
|
+
console.error(chalk.red(`\n❌ Invalid ${label}: "${raw}" — expected a number between 0 and 10\n`));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `morph-spec score <feature> <task> <score> [--dims a,c,q,t]` — record a
|
|
20
|
+
* task eval score under `taskScores` in the authoritative feature.json.
|
|
21
|
+
*
|
|
22
|
+
* The timestamp is stamped server-side (`updatedAt`), the write is atomic
|
|
23
|
+
* (state-manager), attempts auto-increment and bestScore is kept — this
|
|
24
|
+
* replaces the fragile manual Edit of feature.json from the skills.
|
|
25
|
+
*/
|
|
26
|
+
export async function scoreCommand(featureName, taskId, scoreArg, options = {}) {
|
|
27
|
+
try {
|
|
28
|
+
const score = parseScoreValue(scoreArg, 'score');
|
|
29
|
+
|
|
30
|
+
const feature = getFeature(featureName);
|
|
31
|
+
if (!feature) {
|
|
32
|
+
console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let dimensions;
|
|
37
|
+
if (options.dims) {
|
|
38
|
+
const parts = String(options.dims).split(',').map(s => s.trim());
|
|
39
|
+
if (parts.length !== DIM_KEYS.length) {
|
|
40
|
+
console.error(chalk.red(`\n❌ --dims expects ${DIM_KEYS.length} comma-separated values in order a,c,q,t (architecture,contracts,code-quality,test-coverage)\n`));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
dimensions = {};
|
|
44
|
+
DIM_KEYS.forEach((key, i) => {
|
|
45
|
+
dimensions[key] = parseScoreValue(parts[i], `--dims ${key}`);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Non-fatal sanity check: warn when the task id is unknown to tasks.json.
|
|
50
|
+
const tasksPath = join(process.cwd(), '.morph', 'features', featureName, '2-plan', 'tasks.json');
|
|
51
|
+
if (existsSync(tasksPath)) {
|
|
52
|
+
try {
|
|
53
|
+
const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')).tasks || [];
|
|
54
|
+
if (!tasks.some(t => t.id === taskId)) {
|
|
55
|
+
console.error(chalk.yellow(`⚠️ Task "${taskId}" not found in tasks.json — recording anyway`));
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// unreadable tasks.json — the score is still worth recording
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const prev = feature.taskScores?.[taskId];
|
|
63
|
+
const entry = {
|
|
64
|
+
lastScore: score,
|
|
65
|
+
bestScore: prev?.bestScore != null ? Math.max(prev.bestScore, score) : score,
|
|
66
|
+
attempts: (prev?.attempts ?? 0) + 1,
|
|
67
|
+
...(dimensions ? { dimensions } : {}),
|
|
68
|
+
updatedAt: new Date().toISOString(),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
await updateFeature(featureName, `taskScores.${taskId}`, entry);
|
|
72
|
+
|
|
73
|
+
if (options.json) {
|
|
74
|
+
console.log(JSON.stringify({ feature: featureName, task: taskId, ...entry }, null, 2));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(chalk.green(`\n✅ Score recorded: ${featureName}/${taskId} → ${entry.lastScore}`));
|
|
79
|
+
console.log(chalk.gray(` best: ${entry.bestScore} | attempts: ${entry.attempts} | updatedAt: ${entry.updatedAt}`));
|
|
80
|
+
if (dimensions) {
|
|
81
|
+
console.log(chalk.gray(` dims: arch=${dimensions.architecture} contracts=${dimensions.contracts} quality=${dimensions.codeQuality} tests=${dimensions.testCoverage}`));
|
|
82
|
+
}
|
|
83
|
+
console.log('');
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error(chalk.red(`\n❌ Error recording score: ${error.message}\n`));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { parseTasksJson } from '../../lib/tasks/tasks-json-parser.js';
|
|
5
|
+
import { planDispatch } from '../../lib/tasks/dispatch-planner.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `morph-spec dag <feature>` — analyze the tasks.json DAG and print the
|
|
9
|
+
* sequential vs parallel dispatch decision, weighted by effort.
|
|
10
|
+
*
|
|
11
|
+
* This is the command morph-apply §2 runs instead of re-deriving the rules
|
|
12
|
+
* (and instead of the old inline JS snippets the LLM could not execute).
|
|
13
|
+
*/
|
|
14
|
+
export async function dagCommand(featureName, options = {}) {
|
|
15
|
+
try {
|
|
16
|
+
const tasksPath = join(process.cwd(), '.morph', 'features', featureName, '2-plan', 'tasks.json');
|
|
17
|
+
if (!existsSync(tasksPath)) {
|
|
18
|
+
console.error(chalk.red(`\n❌ tasks.json not found for feature "${featureName}"`));
|
|
19
|
+
console.error(chalk.gray(`Expected at: .morph/features/${featureName}/2-plan/tasks.json\n`));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// parseTasksJson validates the schema and throws with the error list.
|
|
24
|
+
const data = await parseTasksJson(tasksPath);
|
|
25
|
+
const plan = planDispatch(data);
|
|
26
|
+
|
|
27
|
+
if (options.json) {
|
|
28
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(chalk.bold(`\n📊 DAG — ${featureName}`));
|
|
33
|
+
console.log('━'.repeat(60));
|
|
34
|
+
|
|
35
|
+
const { stats } = plan;
|
|
36
|
+
console.log(`\nTasks: ${stats.total} total, ${stats.pending} pendente(s)`);
|
|
37
|
+
const groupLine = Object.keys(stats.groups)
|
|
38
|
+
.map(g => `${g}:${stats.groups[g]} (peso ${stats.weights[g]})`)
|
|
39
|
+
.join(' | ');
|
|
40
|
+
if (groupLine) console.log(chalk.gray(`Grupos pendentes: ${groupLine}`));
|
|
41
|
+
|
|
42
|
+
const modeLabel = {
|
|
43
|
+
'sequential': 'SEQUENCIAL (auto-advance)',
|
|
44
|
+
'parallel-flows': 'PARALELO por fluxo de domínio',
|
|
45
|
+
'parallel-roots': 'PARALELO por raízes independentes',
|
|
46
|
+
}[plan.mode];
|
|
47
|
+
console.log(chalk.cyan(`\nDecisão: ${modeLabel}`));
|
|
48
|
+
console.log(` → ${plan.reason}`);
|
|
49
|
+
|
|
50
|
+
for (const flow of plan.flows) {
|
|
51
|
+
console.log(` ${chalk.green('▶')} Fluxo ${chalk.bold(flow.group)} (peso ${flow.weight}): ${flow.taskIds.join(', ')}`);
|
|
52
|
+
}
|
|
53
|
+
if (plan.batch.length > 0) {
|
|
54
|
+
console.log(` ${chalk.green('▶')} Batch paralelo: ${plan.batch.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
if (plan.inline.length > 0) {
|
|
57
|
+
console.log(` ${chalk.yellow('○')} Inline (sem dispatch próprio): ${plan.inline.join(', ')}`);
|
|
58
|
+
}
|
|
59
|
+
if (plan.sequentialRemainder.length > 0) {
|
|
60
|
+
console.log(` ${chalk.gray('→')} Sequencial: ${plan.sequentialRemainder.join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log('\n' + '━'.repeat(60) + '\n');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(chalk.red(`\n❌ Error analyzing DAG: ${error.message}\n`));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|