@polymorphism-tech/morph-spec 6.0.0 → 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.
Files changed (38) hide show
  1. package/README.md +19 -18
  2. package/docs/CHEATSHEET.md +3 -3
  3. package/docs/QUICKSTART.md +4 -4
  4. package/framework/CLAUDE.md +13 -0
  5. package/framework/MORPH.md +57 -36
  6. package/framework/commands/morph-apply.md +4 -21
  7. package/framework/commands/morph-archive.md +1 -1
  8. package/framework/commands/morph-proposal.md +2 -0
  9. package/framework/hooks/claude-code/core/enforce-phase-writes.js +1 -1
  10. package/framework/hooks/claude-code/core/protect-spec-files.js +1 -1
  11. package/framework/hooks/claude-code/core/state-sync.js +212 -0
  12. package/framework/hooks/shared/phase-utils.js +5 -4
  13. package/framework/hooks/shared/state-reader.js +1 -1
  14. package/framework/phases.json +1 -1
  15. package/framework/rules/morph-workflow.md +15 -34
  16. package/framework/schemas/tasks.schema.json +35 -0
  17. package/framework/skills/core/morph-apply/SKILL.md +154 -0
  18. package/framework/skills/core/morph-brainstorming/SKILL.md +53 -30
  19. package/framework/skills/core/morph-implement/SKILL.md +25 -14
  20. package/framework/skills/core/morph-plan/SKILL.md +91 -23
  21. package/framework/skills/core/morph-proposal/SKILL.md +137 -0
  22. package/framework/skills/core/morph-review/SKILL.md +43 -7
  23. package/framework/skills/core/morph-stack-scan/SKILL.md +40 -1
  24. package/framework/standards/STANDARDS.json +4 -5
  25. package/framework/templates/docs/onboarding.md +4 -4
  26. package/framework/templates/tasks.template.json +17 -0
  27. package/package.json +1 -1
  28. package/src/commands/project/init.js +16 -0
  29. package/src/commands/validation/validate-feature.js +1 -1
  30. package/src/core/paths/output-schema.js +6 -6
  31. package/src/core/state/state-manager.js +1 -1
  32. package/src/lib/detectors/design-system-detector.js +1 -1
  33. package/src/lib/installers/memory-installer.js +138 -0
  34. package/src/lib/phase-chain/phase-validator.js +4 -4
  35. package/src/lib/risk-detector.js +80 -0
  36. package/src/lib/tasks/tasks-json-parser.js +185 -0
  37. package/src/scripts/setup-infra.js +25 -0
  38. 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 tarefas de implementação serem concluídas
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 tarefas do tasks.md estarem com status Done
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 relatório ao usuário e aguarde o Gate 3.
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 apresentado ao usuário para aprovação
90
+ - Gate 3 marcado aprovado em state.json (auto ou manual)
56
91
 
57
92
  ## Referências
58
- - `framework/MORPH.md` — seção 5 (Gate 3) e seção 1 (papel Evaluator)
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
- - `framework/evals/trace-schema.md` — formato canônico do trace
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
- ## O que fazer
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-vsa",
186
- "dotnet-vsa",
187
- "nextjs-neon"
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-ui/
98
- │ ├── 2-design/
99
- │ ├── 4-tasks/
100
- │ └── 5-implement/
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "6.0.0",
3
+ "version": "7.0.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -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');
@@ -69,7 +69,7 @@ const PHASE_OUTPUTS = {
69
69
  */
70
70
  const PHASE_FOLDERS = {
71
71
  proposal: '0-proposal',
72
- uiux: '1-ui',
72
+ uiux: '1-design',
73
73
  plan: '2-plan',
74
74
  implement: '3-implement',
75
75
  review: '4-review',
@@ -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-ui/ (optional)
31
- uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
32
- uiMockups: { filename: 'mockups.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
33
- uiComponents: { filename: 'components.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
34
- uiFlows: { filename: 'flows.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
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.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
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/
@@ -34,7 +34,7 @@ export function derivePhase(featurePath) {
34
34
  ['4-review', 'review'],
35
35
  ['3-implement', 'implement'],
36
36
  ['2-plan', 'plan'],
37
- ['1-ui', 'uiux'],
37
+ ['1-design', 'uiux'],
38
38
  ['0-proposal', 'proposal'],
39
39
  ];
40
40
  for (const [folder, phase] of phaseMap) {
@@ -64,7 +64,7 @@ export function detectDesignSystem(projectPath, featureName = null) {
64
64
  projectPath,
65
65
  '.morph/features',
66
66
  featureName,
67
- '1-ui',
67
+ '1-design',
68
68
  'design-system.md'
69
69
  );
70
70
 
@@ -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-ui',
70
- 'mockups.md': '1-ui',
71
- 'components.md': '1-ui',
72
- 'flows.md': '1-ui',
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
+ };