@polymorphism-tech/morph-spec 5.0.1 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -18
- package/docs/CHEATSHEET.md +3 -3
- package/docs/QUICKSTART.md +4 -4
- package/framework/CLAUDE.md +13 -0
- package/framework/MORPH.md +57 -36
- package/framework/commands/morph-apply.md +4 -21
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-proposal.md +2 -0
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +1 -1
- package/framework/hooks/claude-code/core/protect-spec-files.js +1 -1
- package/framework/hooks/claude-code/core/state-sync.js +212 -0
- package/framework/hooks/shared/phase-utils.js +5 -4
- package/framework/hooks/shared/state-reader.js +1 -1
- package/framework/phases.json +1 -1
- package/framework/rules/morph-workflow.md +15 -34
- package/framework/schemas/tasks.schema.json +35 -0
- package/framework/skills/core/morph-apply/SKILL.md +154 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +53 -30
- package/framework/skills/core/morph-implement/SKILL.md +25 -14
- package/framework/skills/core/morph-plan/SKILL.md +91 -23
- package/framework/skills/core/morph-proposal/SKILL.md +137 -0
- package/framework/skills/core/morph-review/SKILL.md +43 -7
- package/framework/skills/core/morph-stack-scan/SKILL.md +40 -1
- package/framework/standards/STANDARDS.json +4 -5
- package/framework/templates/docs/onboarding.md +4 -4
- package/framework/templates/tasks.template.json +17 -0
- package/package.json +1 -1
- package/src/commands/project/init.js +16 -0
- package/src/commands/validation/validate-feature.js +1 -1
- package/src/core/paths/output-schema.js +6 -6
- package/src/core/state/state-manager.js +1 -1
- package/src/lib/detectors/design-system-detector.js +1 -1
- package/src/lib/installers/memory-installer.js +138 -0
- package/src/lib/phase-chain/phase-validator.js +4 -4
- package/src/lib/risk-detector.js +80 -0
- package/src/lib/tasks/tasks-json-parser.js +185 -0
- package/src/scripts/setup-infra.js +25 -0
- package/src/utils/hooks-installer.js +59 -10
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: morph-proposal
|
|
3
|
+
description: Orquestra a fase de proposta+plano de uma feature morph-spec. Encadeia morph-brainstorming (Gate 1) e morph-plan (Gate 2). Em trust:auto pula gates sem sinais de risco.
|
|
4
|
+
version: core
|
|
5
|
+
when-to-invoke: ao receber instrução "use /morph-proposal" do usuário, ou ao iniciar uma nova feature
|
|
6
|
+
user-invokable: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# morph-proposal
|
|
10
|
+
|
|
11
|
+
## Objetivo
|
|
12
|
+
|
|
13
|
+
Conduzir uma feature do request inicial até o plano aprovado (Gate 2), sem intervenção do usuário entre os gates quando `trust: "auto"` e sem sinais de risco.
|
|
14
|
+
|
|
15
|
+
## Quando invocar
|
|
16
|
+
|
|
17
|
+
- Usuário pede "use /morph-proposal {feature}" ou "vamos planejar {feature}".
|
|
18
|
+
- Início de qualquer feature nova num projeto morph-spec.
|
|
19
|
+
- Em sessões retomadas, antes de continuar uma feature em progresso, verificar via `.morph/state.json` se a feature já passou pelos Gates.
|
|
20
|
+
|
|
21
|
+
## Argumentos
|
|
22
|
+
|
|
23
|
+
- `{feature-name}` — nome em kebab-case da feature. Se ausente, perguntar via `AskUserQuestion`.
|
|
24
|
+
|
|
25
|
+
## Pré-condições
|
|
26
|
+
|
|
27
|
+
- `.morph/config/config.json` existe (rode `morph-spec init` antes).
|
|
28
|
+
- Stack está definido (padrão: VSA + .NET 10 + MAF + Next.js + Neon).
|
|
29
|
+
|
|
30
|
+
## Fluxo
|
|
31
|
+
|
|
32
|
+
### 1. Determinar feature name e estado
|
|
33
|
+
|
|
34
|
+
- Se argumento ausente: perguntar via `AskUserQuestion` (1 pergunta, header "Feature").
|
|
35
|
+
- Verificar `.morph/features/{feature-name}/`:
|
|
36
|
+
- Se `0-proposal/proposal.md` existe e tem conteúdo: pular para passo 3.
|
|
37
|
+
- Caso contrário: criar a estrutura e seguir o passo 2.
|
|
38
|
+
|
|
39
|
+
### 2. Invocar `Skill(morph-brainstorming)`
|
|
40
|
+
|
|
41
|
+
Passa o nome da feature. A skill:
|
|
42
|
+
- Faz até 4 perguntas de negócio via `AskUserQuestion`.
|
|
43
|
+
- Invoca `Skill(morph-stack-scan)` para detectar contexto (greenfield curto-circuita).
|
|
44
|
+
- Detecta divergência entre fontes de design-system e marca `signals.designSystemDivergence`.
|
|
45
|
+
- Produz `proposal.md` em `.morph/features/{feature}/0-proposal/`.
|
|
46
|
+
|
|
47
|
+
### 3. Gate 1 (trust-aware)
|
|
48
|
+
|
|
49
|
+
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular sinais de risco:
|
|
50
|
+
|
|
51
|
+
- `designSystemDivergence` — detectado pela morph-brainstorming.
|
|
52
|
+
- `standardsViolations` — violações de mandate listadas na proposal (não deveria, mas verifica).
|
|
53
|
+
|
|
54
|
+
Usar `src/lib/risk-detector.js`:
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
import { shouldPauseGate } from '../../../src/lib/risk-detector.js';
|
|
58
|
+
const { pause, reason } = shouldPauseGate(config, signals);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Se `pause === true`**, apresentar via `AskUserQuestion`:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
question: "Gate 1 — Proposta de {feature} aprovada? Sinal de risco: {reason}"
|
|
65
|
+
options:
|
|
66
|
+
- { label: "aprovado, prosseguir", description: "Aceitar a proposta e avançar para o planejamento" }
|
|
67
|
+
- { label: "preciso ajustar", description: "Apontar mudanças necessárias na proposta" }
|
|
68
|
+
- { label: "cancelar feature", description: "Abortar — feature não vai avançar" }
|
|
69
|
+
header: "Gate 1"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Aguardar resposta. Se "preciso ajustar", aplicar ajustes e reapresentar o gate.
|
|
73
|
+
|
|
74
|
+
**Se `pause === false`**, exibir summary curto e prosseguir:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
## Gate 1 (auto-aprovado)
|
|
78
|
+
Feature: {nome}
|
|
79
|
+
Problema: {1 frase}
|
|
80
|
+
Escopo: {incluído} | Excluído: {exclusões}
|
|
81
|
+
Stack: {compliance}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
O hook `state-sync.js` registra a aprovação automaticamente via PostToolUse em AskUserQuestion. Quando auto-aprovado, marcar manualmente:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
// auto-aprovação: o hook detecta "aprovado" no answer label, mas em auto-mode
|
|
88
|
+
// não há AskUserQuestion. Skill atualiza .morph/state.json via Edit fallback.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 4. Verificar retomada antes de morph-plan
|
|
92
|
+
|
|
93
|
+
- Se `.morph/features/{feature}/2-plan/spec.md` já existe: pular para passo 6 (Gate 2).
|
|
94
|
+
|
|
95
|
+
### 5. Invocar `Skill(morph-plan)`
|
|
96
|
+
|
|
97
|
+
A skill lê `proposal.md` e produz:
|
|
98
|
+
- `spec.md` — especificação técnica detalhada
|
|
99
|
+
- `mandate.md` — contrato de implementação (stack defaults + padrões proibidos)
|
|
100
|
+
- `tasks.json` — task breakdown estruturado validado contra `framework/schemas/tasks.schema.json`
|
|
101
|
+
- `decisions.md` — apenas se houver decisões não triviais
|
|
102
|
+
|
|
103
|
+
### 6. Gate 2 (trust-aware)
|
|
104
|
+
|
|
105
|
+
Sinais de risco para Gate 2 incluem **mais** do que Gate 1:
|
|
106
|
+
|
|
107
|
+
- Todos os do Gate 1.
|
|
108
|
+
- `taskCount` — count(tasks.json.tasks). Threshold: 20.
|
|
109
|
+
- `standardsViolations` detectadas no mandate (improvável, mas verificar).
|
|
110
|
+
|
|
111
|
+
Lógica idêntica ao passo 3 (`shouldPauseGate` + AskUserQuestion ou auto-aprovação).
|
|
112
|
+
|
|
113
|
+
### 7. Conclusão
|
|
114
|
+
|
|
115
|
+
- Sinalizar ao usuário: "Plano aprovado. Rode `/morph-apply {feature}` (ou peça 'implementar {feature}') para iniciar a execução."
|
|
116
|
+
- Não dispachar `morph-apply` automaticamente — usuário decide quando começar.
|
|
117
|
+
|
|
118
|
+
## Anti-padrões
|
|
119
|
+
|
|
120
|
+
- Não pular o Gate 1 mesmo em trust:auto se houver divergência detectada.
|
|
121
|
+
- Não escrever código nesta fase — apenas artefatos de planejamento.
|
|
122
|
+
- Não perguntar sobre stack — o stack padrão está em MORPH.md / mandate.
|
|
123
|
+
|
|
124
|
+
## Output esperado
|
|
125
|
+
|
|
126
|
+
- `.morph/features/{feature}/0-proposal/proposal.md`
|
|
127
|
+
- `.morph/features/{feature}/2-plan/spec.md`
|
|
128
|
+
- `.morph/features/{feature}/2-plan/mandate.md`
|
|
129
|
+
- `.morph/features/{feature}/2-plan/tasks.json`
|
|
130
|
+
- Gates 1 e 2 marcados aprovados em `.morph/state.json` (via hook state-sync).
|
|
131
|
+
|
|
132
|
+
## Referências
|
|
133
|
+
|
|
134
|
+
- `.morph/framework/MORPH.md` — charter NLH completo
|
|
135
|
+
- `framework/skills/core/morph-brainstorming/SKILL.md`
|
|
136
|
+
- `framework/skills/core/morph-plan/SKILL.md`
|
|
137
|
+
- `src/lib/risk-detector.js` — `shouldPauseGate`, `getRiskSignals`
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: morph-review
|
|
3
3
|
description: Conduz a fase de revisão e produz o review-report.md para o Gate 3
|
|
4
4
|
version: core
|
|
5
|
-
when-to-invoke: ao iniciar a fase review, após todas as
|
|
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
7
|
---
|
|
8
8
|
|
|
@@ -13,8 +13,8 @@ Verificar se a implementação atende ao mandate.md e produzir um review-report.
|
|
|
13
13
|
claro o suficiente para que o usuário tome a decisão de aprovação ou rejeição.
|
|
14
14
|
|
|
15
15
|
## Quando invocar
|
|
16
|
-
Ao iniciar a fase review — após todas as
|
|
17
|
-
e o recap.md estar atualizado.
|
|
16
|
+
Ao iniciar a fase review — após todas as tasks de `tasks.json` estarem com status `done`
|
|
17
|
+
e o `recap.md` estar atualizado. Normalmente invocada pela skill orquestradora `morph-apply`.
|
|
18
18
|
|
|
19
19
|
## O que fazer
|
|
20
20
|
|
|
@@ -47,16 +47,52 @@ e o recap.md estar atualizado.
|
|
|
47
47
|
- Preencha `sub_agents_dispatched` com a contagem de tasks onde `sub_agent_dispatched: true`
|
|
48
48
|
- Preencha `completed_at` com o timestamp atual
|
|
49
49
|
|
|
50
|
-
8. Apresente o
|
|
50
|
+
8. Apresente o Gate 3 segundo trust mode.
|
|
51
|
+
|
|
52
|
+
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular sinais de risco:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import { shouldPauseGate } from '../../../src/lib/risk-detector.js';
|
|
56
|
+
const signals = {
|
|
57
|
+
evalScore: phaseScores.composite,
|
|
58
|
+
standardsViolations: blockingItems.filter(b => b.kind === 'standard').map(b => b.id),
|
|
59
|
+
};
|
|
60
|
+
const { pause, reason } = shouldPauseGate(config, signals);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Se `pause === true`**, use `AskUserQuestion`:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
question: "Gate 3 — Review de {feature} aprovado?{ reason ? ' Sinal: ' + reason : '' }"
|
|
67
|
+
header: "Gate 3"
|
|
68
|
+
options:
|
|
69
|
+
- { label: "aprovado, fechar feature", description: "Marcar feature como completed, habilitar /morph-archive" }
|
|
70
|
+
- { label: "preciso ajustar", description: "Apontar correções necessárias antes de fechar" }
|
|
71
|
+
- { label: "rollback", description: "Desfazer e voltar para implement" }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Se `pause === false`**, exibir summary curto e prosseguir:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
## Gate 3 (auto-aprovado)
|
|
78
|
+
|
|
79
|
+
**Composite score:** {score}/10
|
|
80
|
+
**Dimensões:** arquitetura={X} contratos={Y} qualidade={Z} cobertura={W}
|
|
81
|
+
**Bloqueantes:** {N (zero esperado em auto-aprovação)}
|
|
82
|
+
**Recomendação:** Aprovado
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
O hook state-sync detecta a aprovação em answers de AskUserQuestion e marca `features[X].approvalGates.review = true`. Em auto sem AskUserQuestion, a skill atualiza state.json direto.
|
|
51
86
|
|
|
52
87
|
## Output esperado
|
|
53
88
|
- `.morph/features/{feature}/4-review/review-report.md` com scores e recomendação
|
|
54
89
|
- Trace da fase com `phase_summary` preenchido
|
|
55
|
-
- Gate 3
|
|
90
|
+
- Gate 3 marcado aprovado em state.json (auto ou manual)
|
|
56
91
|
|
|
57
92
|
## Referências
|
|
58
|
-
-
|
|
93
|
+
- `.morph/framework/MORPH.md` — seção 5 (Gate 3), seção 1 (papel Evaluator), Trust Mode
|
|
59
94
|
- `framework/skills/core/morph-eval/SKILL.md` — para calcular scores por dimensão
|
|
60
|
-
-
|
|
95
|
+
- `.morph/framework/evals/trace-schema.md` — formato canônico do trace
|
|
61
96
|
- `.morph/features/{feature}/2-plan/mandate.md` — critérios de done da feature
|
|
62
97
|
- `.morph/features/{feature}/3-implement/recap.md` — registro de execução
|
|
98
|
+
- `src/lib/risk-detector.js` — `shouldPauseGate` para Gate 3
|
|
@@ -30,7 +30,46 @@ Quando o projeto já tem código existente e você precisa entender o estado atu
|
|
|
30
30
|
|
|
31
31
|
**Padrões proibidos:** `Guid.NewGuid()`, classes não-sealed sem justificativa, exceptions como fluxo de controle, Repository pattern, `IRepository<T>`.
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Greenfield short-circuit
|
|
34
|
+
|
|
35
|
+
Antes de despachar Explore, verifique se o projeto é greenfield:
|
|
36
|
+
|
|
37
|
+
1. Use `Glob` para detectar presença de qualquer um destes:
|
|
38
|
+
- `**/*.csproj`
|
|
39
|
+
- `**/*.sln`
|
|
40
|
+
- `package.json` (na raiz)
|
|
41
|
+
- `src/**/*.cs` ou `src/**/*.ts` ou `src/**/*.tsx`
|
|
42
|
+
- `migrations/**/*`
|
|
43
|
+
|
|
44
|
+
2. Se **nenhum** for encontrado, o projeto é greenfield. Produza este `stack-scan.md` em `.morph/features/{feature}/0-proposal/` e retorne sem chamar Explore:
|
|
45
|
+
|
|
46
|
+
```markdown
|
|
47
|
+
# Stack Scan — {feature}
|
|
48
|
+
|
|
49
|
+
**Status:** GREENFIELD (sem código source detectado)
|
|
50
|
+
|
|
51
|
+
## Stack
|
|
52
|
+
Project empty. Aplicar stack padrão de MORPH.md:
|
|
53
|
+
- Backend: .NET 10 + VSA + EF Core + Neon
|
|
54
|
+
- Frontend: Next.js App Router + Tailwind + Shadcn + Framer Motion
|
|
55
|
+
- IA: MAF (quando aplicável à feature)
|
|
56
|
+
- Jobs: Hangfire (apenas se necessário)
|
|
57
|
+
|
|
58
|
+
## Padrões obrigatórios
|
|
59
|
+
- IDs: `Guid.CreateVersion7()`
|
|
60
|
+
- Classes: `sealed` por padrão
|
|
61
|
+
- Erros: Result pattern (`result.Match()`)
|
|
62
|
+
|
|
63
|
+
## Padrões proibidos
|
|
64
|
+
`Guid.NewGuid()`, classes não-sealed sem justificativa, exceptions como fluxo de controle, Repository pattern, `IRepository<T>`.
|
|
65
|
+
|
|
66
|
+
## Recomendação
|
|
67
|
+
Compliance trivial (greenfield). Prosseguir para morph-brainstorming.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
3. Se algum dos arquivos foi encontrado, o projeto é brownfield — siga para o passo seguinte.
|
|
71
|
+
|
|
72
|
+
## O que fazer (brownfield)
|
|
34
73
|
|
|
35
74
|
1. Despache um `Explore` subagent para varrer o codebase:
|
|
36
75
|
|
|
@@ -182,9 +182,9 @@
|
|
|
182
182
|
},
|
|
183
183
|
"digest": "Chunk documents with 512 token size and 50 token overlap for RAG. Embed with text-embedding-3-small. Store embedding + metadata (source, chunk index). Retrieve top-k=5 by cosine similarity. Re-rank before injecting into LLM context. Use pgvector HNSW index for performance at scale.",
|
|
184
184
|
"stacks": [
|
|
185
|
-
"dotnet
|
|
186
|
-
"
|
|
187
|
-
"
|
|
185
|
+
"dotnet",
|
|
186
|
+
"nextjs",
|
|
187
|
+
"neon"
|
|
188
188
|
]
|
|
189
189
|
},
|
|
190
190
|
{
|
|
@@ -1126,8 +1126,7 @@
|
|
|
1126
1126
|
},
|
|
1127
1127
|
"digest": "Guidelines for generating project images with Gemini Image MCP — prompt patterns, aspect ratios, design-system-aware prompts, iterative editing, and assets.md format.",
|
|
1128
1128
|
"stacks": [
|
|
1129
|
-
"nextjs"
|
|
1130
|
-
"next"
|
|
1129
|
+
"nextjs"
|
|
1131
1130
|
]
|
|
1132
1131
|
}
|
|
1133
1132
|
]
|
|
@@ -94,10 +94,10 @@ morph-spec doctor
|
|
|
94
94
|
│ ├── context/ # Project context & overrides
|
|
95
95
|
│ └── features/{feature}/ # Feature outputs
|
|
96
96
|
│ ├── 0-proposal/
|
|
97
|
-
│ ├── 1-
|
|
98
|
-
│ ├── 2-
|
|
99
|
-
│ ├──
|
|
100
|
-
│ └──
|
|
97
|
+
│ ├── 1-design/ # optional (UI-heavy features)
|
|
98
|
+
│ ├── 2-plan/
|
|
99
|
+
│ ├── 3-implement/
|
|
100
|
+
│ └── 4-review/
|
|
101
101
|
└── .claude/
|
|
102
102
|
├── commands/ # Slash commands
|
|
103
103
|
├── skills/ # Workflow skills
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"feature": "{{featureName}}",
|
|
4
|
+
"tasks": [
|
|
5
|
+
{
|
|
6
|
+
"id": "T1",
|
|
7
|
+
"title": "Example task title",
|
|
8
|
+
"description": "What this task accomplishes and why",
|
|
9
|
+
"dependencies": [],
|
|
10
|
+
"effort": "M",
|
|
11
|
+
"doneCriteria": "Verifiable acceptance condition stated in one sentence",
|
|
12
|
+
"status": "pending",
|
|
13
|
+
"outputs": [],
|
|
14
|
+
"group": "backend"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -483,6 +483,19 @@ export async function initCommand(options) {
|
|
|
483
483
|
|
|
484
484
|
logger.blank();
|
|
485
485
|
|
|
486
|
+
// Populate Claude Code persistent memory so future sessions enter contextualized
|
|
487
|
+
try {
|
|
488
|
+
const { installProjectMemory } = await import('../../lib/installers/memory-installer.js');
|
|
489
|
+
const memResult = await installProjectMemory(targetPath, {
|
|
490
|
+
projectName: config.project?.name ?? targetPath.split(/[\\/]/).pop(),
|
|
491
|
+
frameworkVersion: config.frameworkVersion ?? cliVersion,
|
|
492
|
+
stack: config.project?.stack,
|
|
493
|
+
});
|
|
494
|
+
logger.dim(` ✓ Claude Code memory populated (${memResult.projectMemoryPath})`);
|
|
495
|
+
} catch (err) {
|
|
496
|
+
logger.dim(` ⚠ Memory install skipped: ${err.message}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
486
499
|
// Suggest tutorial for first-time users
|
|
487
500
|
if (integrationsCreated) {
|
|
488
501
|
logger.blank();
|
|
@@ -490,6 +503,9 @@ export async function initCommand(options) {
|
|
|
490
503
|
}
|
|
491
504
|
|
|
492
505
|
logger.blank();
|
|
506
|
+
logger.warn('IMPORTANT: Restart your Claude Code session for morph-* skills to be available.');
|
|
507
|
+
logger.dim('Skills installed in .claude/skills/ are only registered at session start.');
|
|
508
|
+
logger.blank();
|
|
493
509
|
|
|
494
510
|
} catch (error) {
|
|
495
511
|
spinner.fail('Installation failed');
|
|
@@ -27,16 +27,16 @@ export const OUTPUT_SCHEMA = {
|
|
|
27
27
|
stackScan: { filename: 'stack-scan.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
|
|
28
28
|
migrationPlan: { filename: 'migration-plan.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
|
|
29
29
|
|
|
30
|
-
// 1-
|
|
31
|
-
uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-
|
|
32
|
-
uiMockups: { filename: 'mockups.md', phaseDir: '1-
|
|
33
|
-
uiComponents: { filename: 'components.md', phaseDir: '1-
|
|
34
|
-
uiFlows: { filename: 'flows.md', phaseDir: '1-
|
|
30
|
+
// 1-design/ (optional)
|
|
31
|
+
uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
32
|
+
uiMockups: { filename: 'mockups.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
33
|
+
uiComponents: { filename: 'components.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
34
|
+
uiFlows: { filename: 'flows.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
35
35
|
|
|
36
36
|
// 2-plan/
|
|
37
37
|
spec: { filename: 'spec.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
38
38
|
mandate: { filename: 'mandate.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
39
|
-
tasks: { filename: 'tasks.
|
|
39
|
+
tasks: { filename: 'tasks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
40
40
|
decisions: { filename: 'decisions.md', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
41
41
|
|
|
42
42
|
// 3-implement/
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Installer — populates Claude Code persistent memory during morph-init.
|
|
3
|
+
*
|
|
4
|
+
* Writes two memory files (project + reference) plus an entry in MEMORY.md
|
|
5
|
+
* so future Claude Code sessions enter pre-contextualized about the
|
|
6
|
+
* morph-spec setup of this project.
|
|
7
|
+
*
|
|
8
|
+
* Memory location: ~/.claude/projects/<encoded-project-path>/memory/
|
|
9
|
+
*
|
|
10
|
+
* The path encoding mirrors Claude Code's own scheme: replace slashes and
|
|
11
|
+
* colons with dashes. Tests can override via MORPH_MEMORY_ROOT env var.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs-extra';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { homedir } from 'os';
|
|
17
|
+
|
|
18
|
+
function getMemoryRoot(projectPath) {
|
|
19
|
+
if (process.env.MORPH_MEMORY_ROOT) return process.env.MORPH_MEMORY_ROOT;
|
|
20
|
+
|
|
21
|
+
const encoded = projectPath.replace(/[:\\/]/g, '-').replace(/^-+/, '');
|
|
22
|
+
return join(homedir(), '.claude', 'projects', encoded, 'memory');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function projectMemoryContent(info) {
|
|
26
|
+
return `---
|
|
27
|
+
name: project-morph-spec
|
|
28
|
+
description: morph-spec project configuration, paths, slash commands, and trust mode
|
|
29
|
+
metadata:
|
|
30
|
+
type: project
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
# ${info.projectName} — morph-spec project
|
|
34
|
+
|
|
35
|
+
**Framework version:** ${info.frameworkVersion}
|
|
36
|
+
**Stack:** ${info.stack ?? 'default (nextjs + dotnet + VSA + MAF + Neon)'}
|
|
37
|
+
**Trust mode:** auto (gates auto-pass without risk signals)
|
|
38
|
+
|
|
39
|
+
## Key paths
|
|
40
|
+
|
|
41
|
+
- \`.morph/framework/MORPH.md\` — NLH charter (read once per session)
|
|
42
|
+
- \`.morph/framework/agents.json\` — 7 domain personas
|
|
43
|
+
- \`.morph/framework/standards/STANDARDS.json\` — standards index (load on demand)
|
|
44
|
+
- \`.morph/framework/schemas/tasks.schema.json\` — JSON Schema for tasks.json
|
|
45
|
+
- \`.morph/features/{feature}/\` — feature artifacts: 0-proposal / 1-design / 2-plan / 3-implement / 4-review
|
|
46
|
+
- \`.morph/context/\` — long-lived architectural context (design-system, decisions)
|
|
47
|
+
- \`.morph/state.json\` — feature state (managed by state-sync hook; Edit fallback allowed)
|
|
48
|
+
|
|
49
|
+
## Slash commands
|
|
50
|
+
|
|
51
|
+
| Command | Skill invoked |
|
|
52
|
+
|---------|---------------|
|
|
53
|
+
| \`/morph-proposal {feature}\` | \`Skill(morph-proposal)\` — brainstorming → Gate 1 → plan → Gate 2 |
|
|
54
|
+
| \`/morph-apply {feature}\` | \`Skill(morph-apply)\` — DAG-adaptive implement loop → review → Gate 3 |
|
|
55
|
+
| \`/morph-status [feature]\` | inspect state.json |
|
|
56
|
+
| \`/morph-archive {feature}\` | move completed feature to .morph/archive/ |
|
|
57
|
+
| \`/morph-preflight\` | pre-deploy validation |
|
|
58
|
+
| \`/morph-troubleshoot [erro]\` | diagnose .NET/Next.js errors |
|
|
59
|
+
|
|
60
|
+
## Core skills
|
|
61
|
+
|
|
62
|
+
\`morph-proposal\`, \`morph-apply\`, \`morph-brainstorming\`, \`morph-plan\`, \`morph-implement\`, \`morph-eval\`, \`morph-review\`, \`morph-stack-scan\`, \`morph-standards\`
|
|
63
|
+
|
|
64
|
+
**Why:** This project is managed by morph-spec — spec-driven autonomous development with trust-aware gates.
|
|
65
|
+
|
|
66
|
+
**How to apply:**
|
|
67
|
+
- When user mentions a feature, check \`.morph/features/\` first.
|
|
68
|
+
- When user types \`/morph-proposal\`, invoke \`Skill(morph-proposal)\` directly.
|
|
69
|
+
- In \`trust: "auto"\`, gates auto-pass; only AskUserQuestion on risk signals.
|
|
70
|
+
- Prefer morph-* skills over superpowers:* equivalents in this project.
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const REFERENCE_MEMORY_CONTENT = `---
|
|
75
|
+
name: reference-morph-context
|
|
76
|
+
description: where to find long-lived morph-spec project context and schema files
|
|
77
|
+
metadata:
|
|
78
|
+
type: reference
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
# morph-spec context locations
|
|
82
|
+
|
|
83
|
+
- \`.morph/context/README.md\` — generated architecture overview
|
|
84
|
+
- \`.morph/context/design-system.md\` — design tokens and components (source of truth)
|
|
85
|
+
- \`.morph/framework/MORPH.md\` — NLH charter, Trust Mode rules, gates, skills table
|
|
86
|
+
- \`.morph/framework/standards/STANDARDS.json\` — index; load specific standard via \`path\` field
|
|
87
|
+
- \`.morph/framework/schemas/tasks.schema.json\` — tasks.json schema reference
|
|
88
|
+
- \`.morph/framework/phases.json\` — phase folder mapping and skill associations
|
|
89
|
+
- \`.morph/framework/agents.json\` — domain persona definitions (dotnet-senior, ef-modeler, etc.)
|
|
90
|
+
- \`.morph/framework/rubrics/\` — eval rubrics for morph-eval scoring
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Populate Claude Code persistent memory for a morph-spec project.
|
|
95
|
+
*
|
|
96
|
+
* Idempotent: re-running overwrites the memory files with current info
|
|
97
|
+
* but appends MEMORY.md index entries only if missing.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} projectPath - Absolute path of the project root
|
|
100
|
+
* @param {object} info - Project facts to embed in the memory
|
|
101
|
+
* @param {string} info.projectName
|
|
102
|
+
* @param {string} info.frameworkVersion
|
|
103
|
+
* @param {string} [info.stack]
|
|
104
|
+
* @returns {Promise<{ projectMemoryPath: string, referenceMemoryPath: string, indexPath: string }>}
|
|
105
|
+
*/
|
|
106
|
+
export async function installProjectMemory(projectPath, info) {
|
|
107
|
+
const memoryRoot = getMemoryRoot(projectPath);
|
|
108
|
+
await fs.ensureDir(memoryRoot);
|
|
109
|
+
|
|
110
|
+
const projectMemoryPath = join(memoryRoot, 'project_morph_spec.md');
|
|
111
|
+
await fs.writeFile(projectMemoryPath, projectMemoryContent(info), 'utf8');
|
|
112
|
+
|
|
113
|
+
const referenceMemoryPath = join(memoryRoot, 'reference_morph_context.md');
|
|
114
|
+
await fs.writeFile(referenceMemoryPath, REFERENCE_MEMORY_CONTENT, 'utf8');
|
|
115
|
+
|
|
116
|
+
const indexPath = join(memoryRoot, 'MEMORY.md');
|
|
117
|
+
let indexContent = '';
|
|
118
|
+
if (await fs.pathExists(indexPath)) {
|
|
119
|
+
indexContent = await fs.readFile(indexPath, 'utf8');
|
|
120
|
+
} else {
|
|
121
|
+
indexContent = '# Memory Index\n';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const projectLine = '- [morph-spec project](project_morph_spec.md) — stack, trust mode, slash commands, key paths';
|
|
125
|
+
const refLine = '- [morph-spec context](reference_morph_context.md) — where to find long-lived context';
|
|
126
|
+
|
|
127
|
+
if (!indexContent.includes('project_morph_spec.md')) {
|
|
128
|
+
if (!indexContent.endsWith('\n')) indexContent += '\n';
|
|
129
|
+
indexContent += projectLine + '\n';
|
|
130
|
+
}
|
|
131
|
+
if (!indexContent.includes('reference_morph_context.md')) {
|
|
132
|
+
indexContent += refLine + '\n';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await fs.writeFile(indexPath, indexContent, 'utf8');
|
|
136
|
+
|
|
137
|
+
return { projectMemoryPath, referenceMemoryPath, indexPath };
|
|
138
|
+
}
|
|
@@ -66,10 +66,10 @@ const OUTPUT_PHASE_MAP = {
|
|
|
66
66
|
'spec.md': '2-plan',
|
|
67
67
|
'mandate.md': '2-plan',
|
|
68
68
|
'decisions.md': '2-plan',
|
|
69
|
-
'design-system.md': '1-
|
|
70
|
-
'mockups.md': '1-
|
|
71
|
-
'components.md': '1-
|
|
72
|
-
'flows.md': '1-
|
|
69
|
+
'design-system.md': '1-design',
|
|
70
|
+
'mockups.md': '1-design',
|
|
71
|
+
'components.md': '1-design',
|
|
72
|
+
'flows.md': '1-design',
|
|
73
73
|
'tasks.md': '2-plan',
|
|
74
74
|
'recap.md': '3-implement',
|
|
75
75
|
'review-report.md': '4-review'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Risk Detector — decides whether a gate should pause execution.
|
|
3
|
+
*
|
|
4
|
+
* In `trust: "auto"` mode (default), gates auto-pass UNLESS one of these
|
|
5
|
+
* risk signals triggers a forced pause. In `trust: "manual"` mode, gates
|
|
6
|
+
* always pause via AskUserQuestion.
|
|
7
|
+
*
|
|
8
|
+
* Signals checked (in order):
|
|
9
|
+
* - designSystemDivergence: boolean — conflicting design sources detected
|
|
10
|
+
* - standardsViolations: string[] — violations of mandate.md proibições
|
|
11
|
+
* - evalScore: number — morph-eval score (pause if < 8)
|
|
12
|
+
* - taskCount: number — total tasks (pause if > 20)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const SCOPE_TASK_THRESHOLD = 20;
|
|
16
|
+
const MIN_EVAL_SCORE = 8;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} config - .morph/config/config.json contents (uses `trust`)
|
|
20
|
+
* @param {object} signals
|
|
21
|
+
* @returns {{ pause: boolean, reason: string }}
|
|
22
|
+
*/
|
|
23
|
+
export function shouldPauseGate(config, signals) {
|
|
24
|
+
const trust = config?.trust ?? 'auto';
|
|
25
|
+
|
|
26
|
+
if (trust === 'manual') {
|
|
27
|
+
return { pause: true, reason: 'trust mode is manual' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (signals.designSystemDivergence) {
|
|
31
|
+
return { pause: true, reason: 'design-system divergence detected' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (Array.isArray(signals.standardsViolations) && signals.standardsViolations.length > 0) {
|
|
35
|
+
return {
|
|
36
|
+
pause: true,
|
|
37
|
+
reason: `standards violations detected: ${signals.standardsViolations.join(', ')}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof signals.evalScore === 'number' && signals.evalScore < MIN_EVAL_SCORE) {
|
|
42
|
+
return {
|
|
43
|
+
pause: true,
|
|
44
|
+
reason: `eval score ${signals.evalScore} below threshold ${MIN_EVAL_SCORE}`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof signals.taskCount === 'number' && signals.taskCount > SCOPE_TASK_THRESHOLD) {
|
|
49
|
+
return {
|
|
50
|
+
pause: true,
|
|
51
|
+
reason: `scope of ${signals.taskCount} tasks exceeds threshold ${SCOPE_TASK_THRESHOLD}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { pause: false, reason: '' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Human-readable list of risk signals present (for logging/Gate summary).
|
|
60
|
+
* @returns {string[]}
|
|
61
|
+
*/
|
|
62
|
+
export function getRiskSignals(signals) {
|
|
63
|
+
const out = [];
|
|
64
|
+
if (signals.designSystemDivergence) out.push('design-system divergence');
|
|
65
|
+
if (Array.isArray(signals.standardsViolations) && signals.standardsViolations.length > 0) {
|
|
66
|
+
out.push(`standards violations: ${signals.standardsViolations.join(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
if (typeof signals.evalScore === 'number' && signals.evalScore < MIN_EVAL_SCORE) {
|
|
69
|
+
out.push(`eval score ${signals.evalScore}/10`);
|
|
70
|
+
}
|
|
71
|
+
if (typeof signals.taskCount === 'number' && signals.taskCount > SCOPE_TASK_THRESHOLD) {
|
|
72
|
+
out.push(`${signals.taskCount} tasks (over threshold)`);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const RISK_CONSTANTS = {
|
|
78
|
+
SCOPE_TASK_THRESHOLD,
|
|
79
|
+
MIN_EVAL_SCORE,
|
|
80
|
+
};
|