@polymorphism-tech/morph-spec 9.5.0 → 9.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 +13 -5
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +3 -3
- package/framework/docs/CLI.md +43 -11
- package/framework/evals/review-checklist.md +2 -2
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/hooks/claude-code/core/README.md +4 -0
- package/framework/hooks/claude-code/core/heavy-gate.js +5 -3
- package/framework/hooks/claude-code/core/test-budget.js +89 -0
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/feature-locator.js +7 -5
- package/framework/hooks/shared/heavy-command.js +72 -1
- package/framework/hooks/shared/heavy-config.js +28 -1
- package/framework/hooks/shared/heavy-slots.js +41 -5
- package/framework/hooks/shared/host-services.js +433 -0
- package/framework/schemas/config.schema.json +65 -2
- 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 +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +3 -3
- package/framework/skills/core/morph-review/SKILL.md +3 -2
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +2 -1
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +15 -1
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +50 -24
- package/src/commands/observability/cost.js +12 -0
- package/src/commands/project/env.js +3 -3
- package/src/commands/project/heavy.js +10 -3
- package/src/commands/project/worktree.js +9 -4
- package/src/commands/state/advance.js +3 -0
- package/src/commands/state/create.js +3 -3
- package/src/commands/state/delete.js +1 -1
- package/src/commands/state/finish.js +20 -4
- package/src/commands/validation/mutate.js +2 -2
- package/src/commands/validation/plan-check.js +1 -0
- package/src/commands/validation/verify.js +3 -0
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/e2e/compose-env.js +26 -3
- package/src/lib/e2e/stack-teardown.js +19 -4
- package/src/lib/heavy/gate.js +23 -4
- package/src/lib/heavy/live-stacks.js +64 -0
- package/src/lib/review-reports.js +21 -10
- package/src/lib/risk-detector.js +9 -1
- package/src/lib/tasks/test-runner.js +3 -0
- package/src/lib/telemetry/metrics.js +47 -3
- package/src/lib/validators/packages/package-validator.js +2 -2
- package/src/lib/worktree/docker-audit.js +7 -3
- package/src/lib/worktree/env-allocator.js +27 -5
- package/src/lib/worktree/ignored-triage.js +8 -0
- package/src/utils/agents-installer.js +4 -1
- package/src/utils/file-copier.js +26 -4
- package/src/utils/hooks-installer.js +4 -4
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"title": "MORPH-SPEC .morph/config/config.json",
|
|
4
|
-
"description": "Describes project.tests[] — the plural declaration of the project's test suites
|
|
4
|
+
"description": "Describes project.tests[] — the plural declaration of the project's test suites —, e2e.hostServices and finishBackup.retention. Everything else in config.json is deliberately left open (additionalProperties: true) so that configs already in the field never break on a key this schema has not heard of. The authorities at runtime are validateTestNodes() in src/lib/tasks/test-nodes.js and resolveBackupRetention() in src/lib/worktree/finish-backup.js: a schema nothing calls is decoration.",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": true,
|
|
7
7
|
"properties": {
|
|
@@ -119,6 +119,58 @@
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
},
|
|
122
|
+
"e2e": {
|
|
123
|
+
"type": "object",
|
|
124
|
+
"additionalProperties": true,
|
|
125
|
+
"description": "The verification stack of `e2e up` and of the verify e2e node. Only hostServices is described here; the other keys (auth, command, portEnvNames, ...) stay open.",
|
|
126
|
+
"properties": {
|
|
127
|
+
"hostServices": {
|
|
128
|
+
"type": "array",
|
|
129
|
+
"description": "Apps that run as host processes next to the compose stack, when building them inside the Docker VM is expensive: compose keeps only the dependencies. Order on `e2e up` and on the verify e2e node: compose up --wait, then each service in array order (build, start detached, health), then the auth seed. They stay up until `e2e down`, `finish`, `delete` or `worktree remove`. Reused while the code stamp (HEAD + status + diff) matches and health is green; a divergent stamp or --fresh/--fresh-e2e kills, rebuilds and restarts them. A failure (build exit != 0, timeout, health never green) fails `e2e up` (exit 1) and the e2e node with the service name and its log tail, kills what this run started and skips the seed. The authority at runtime is validateServices() in framework/hooks/shared/host-services.js.",
|
|
130
|
+
"items": {
|
|
131
|
+
"type": "object",
|
|
132
|
+
"additionalProperties": false,
|
|
133
|
+
"required": ["name", "start"],
|
|
134
|
+
"properties": {
|
|
135
|
+
"name": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"pattern": "^[A-Za-z0-9._-]+$",
|
|
138
|
+
"description": "Unique in the array. Names the log file (<git-common-dir>/morph-spec/e2e-host-<project>-<name>.log) and every message about the service."
|
|
139
|
+
},
|
|
140
|
+
"cwd": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"default": ".",
|
|
143
|
+
"description": "Working directory of build and start, relative to the project root. One that does not exist refuses."
|
|
144
|
+
},
|
|
145
|
+
"build": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"description": "Runs before start; a non-zero exit fails the bring-up."
|
|
148
|
+
},
|
|
149
|
+
"start": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"minLength": 1,
|
|
152
|
+
"description": "The process, started detached; its output goes to the service log."
|
|
153
|
+
},
|
|
154
|
+
"env": {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"additionalProperties": { "type": ["string", "number"] },
|
|
157
|
+
"description": "Added on top of process.env and the stack env (port block + e2e.auth.envFile). ${VAR} is resolved from the stack env, then process.env; a VAR with no value (or an empty one) refuses before any build, naming the variable and the service."
|
|
158
|
+
},
|
|
159
|
+
"health": {
|
|
160
|
+
"type": "string",
|
|
161
|
+
"description": "URL polled until it answers with an HTTP status below 500. ${VAR} interpolates like env. Without it, a process still alive 2 s after start counts as ready."
|
|
162
|
+
},
|
|
163
|
+
"timeoutMs": {
|
|
164
|
+
"type": "number",
|
|
165
|
+
"exclusiveMinimum": 0,
|
|
166
|
+
"default": 180000,
|
|
167
|
+
"description": "Ceiling for build + start + health together."
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
122
174
|
"finishBackup": {
|
|
123
175
|
"type": "object",
|
|
124
176
|
"additionalProperties": true,
|
|
@@ -179,7 +231,18 @@
|
|
|
179
231
|
"type": "number",
|
|
180
232
|
"minimum": 0,
|
|
181
233
|
"default": 4,
|
|
182
|
-
"description": "Free RAM (os.freemem) below which a heavy node WAITS instead of starting and dying 'killed' for memory. It waits with or without a slot holder - the pressure often comes from outside (WSL vmmem, the IDE). 0 disables the check."
|
|
234
|
+
"description": "Free RAM (os.freemem) below which a heavy node WAITS instead of starting and dying 'killed' for memory. It waits with or without a slot holder - the pressure often comes from outside (WSL vmmem, the IDE). 0 disables the check. Since the per-kind floors exist it is the fallback: set explicitly, it applies to every kind not listed in minFreeMemGbByKind (so 0 still turns the check off everywhere)."
|
|
235
|
+
},
|
|
236
|
+
"minFreeMemGbByKind": {
|
|
237
|
+
"type": "object",
|
|
238
|
+
"additionalProperties": false,
|
|
239
|
+
"default": { "unit": 1.5, "build": 4, "e2e": 4 },
|
|
240
|
+
"description": "Free-RAM floor per kind of heavy command. unit = vitest/jest, npm|pnpm|yarn test, the verify tests node; e2e = playwright, docker compose up, the verify e2e node, e2e up; build = everything else (dotnet build|test|publish, next build, docker build, mutate, unknown commands). A verify admitted at a lighter node waits again, holding its slot, before a heavier one. A kind left out takes an explicit heavy.minFreeMemGb, else its default.",
|
|
241
|
+
"properties": {
|
|
242
|
+
"unit": { "type": "number", "minimum": 0 },
|
|
243
|
+
"build": { "type": "number", "minimum": 0 },
|
|
244
|
+
"e2e": { "type": "number", "minimum": 0 }
|
|
245
|
+
}
|
|
183
246
|
},
|
|
184
247
|
"waitTimeoutSec": {
|
|
185
248
|
"type": "number",
|
|
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando for iniciar um novo projeto de LP com o template Umbraco genérico
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- umbraco
|
|
10
10
|
---
|
|
@@ -4,7 +4,7 @@ description: Migra uma landing page existente (React/Vite, Next.js estático ou
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando o usuário pedir "use /migrate-lp-to-umbraco {projeto}" ou descrever a necessidade de migrar uma LP para Umbraco
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- umbraco
|
|
10
10
|
---
|
|
@@ -4,7 +4,7 @@ description: Executa as fatias do plano de uma feature morph-spec — dispatch p
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use com o gate do plano aprovado, em "use /morph-apply {feature}" ou "implementar feature"
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-apply
|
|
@@ -4,7 +4,7 @@ description: ADW cirúrgico de hotfix — resolve uma queda/bug crítico de prod
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando chegar "/morph-hotfix {feature}", quando a morph-plan rotear o pedido como workType hotfix, ou quando o usuário disser "produção caiu / hotfix / urgente" a respeito de um incidente
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-hotfix
|
|
@@ -4,7 +4,7 @@ description: Cuida da RAM e da CPU da máquina enquanto o morph-spec trabalha
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando a máquina estiver lenta ou sem RAM, um comando pesado esperou ou foi recusado pela fila, sobrou worktree de sub-agent, ou vários sub-agents pesados vão rodar em paralelo
|
|
6
6
|
user-invocable: false
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-machine
|
|
@@ -4,7 +4,7 @@ description: Conduz a AUTORIA de mídia gerada por IA — escolher o modelo pelo
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando a tarefa for PRODUZIR imagem ou vídeo por IA (catálogo, marketing, asset de LP) e alguém precisar decidir modelo, prompt e aprovação
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- dotnet
|
|
10
10
|
- nextjs
|
|
@@ -4,7 +4,7 @@ description: Leva uma feature do pedido ao plano aprovado — roteia o tipo de t
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando uma feature, bug ou chore começar ("/morph-plan {feature}", "vamos planejar X"), ou ao retomar uma feature cujo gate plan ainda não foi aprovado
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -71,7 +71,7 @@ O `create` já escreveu o esqueleto em `2-plan/plan.md` (leia e preencha; `chore
|
|
|
71
71
|
- **Observável**: que asserção (ou que linha de código) prova? Sem resposta, reescreva ou descarte.
|
|
72
72
|
- **Artefato não é comportamento.** Cenário que cita `grep`, "contém a string", "o binário", "nenhum arquivo", "arquivo/pasta/diretório em" ou glob `**/` descreve o repositório. Tente o efeito em runtime; se o artefato É o entregável, justifique em `> Por que artefato:` logo abaixo (nunca na mesma linha, nunca `> [!note]`).
|
|
73
73
|
- **Saída de LLM** (o que o modelo responde) prova-se por `eval` de runtime, nunca pelo texto do prompt: evidência em `3-implement/evidencias/{Cn}.md`, path listado em `arquivos:` da fatia; com nó `runner: "evals"` (standard `ai-agents-evals-with-cache`) o cenário mora no dataset. No máximo 1 teste de estrutura da fonte do prompt.
|
|
74
|
-
- **"O comando de teste da raiz roda de ponta a ponta"** prova-se pelo `verify {feature}` (o nó `e2e` e o nó de teste `e2e: true` já percorrem a cadeia): nomeie essa rodada como a prova, nunca uma execução avulsa. Precisando mesmo rodar fora dele, numa linha só (o Bash não guarda o env entre chamadas): `eval "$(morph-spec env {feature})" && morph-spec heavy run -- <comando>` — o `COMPOSE_PROJECT_NAME` da feature é o que o `finish` derruba com os volumes. `prova: manual` registra passo e resultado em `3-implement/evidencias/{Cn}.md`.
|
|
74
|
+
- **"O comando de teste da raiz roda de ponta a ponta"** prova-se pelo `verify {feature}` (o nó `e2e` e o nó de teste `e2e: true` já percorrem a cadeia): nomeie essa rodada como a prova, nunca uma execução avulsa. Precisando mesmo rodar fora dele, numa linha só (o Bash não guarda o env entre chamadas): `eval "$(morph-spec env {feature} --with-secrets)" && morph-spec heavy run -- <comando>` — o `COMPOSE_PROJECT_NAME` da feature é o que o `finish` derruba com os volumes. `prova: manual` registra passo e resultado em `3-implement/evidencias/{Cn}.md`.
|
|
75
75
|
- **Orçamento de teste**: um teste por cenário de risco — o cenário é a unidade, nunca a pós-condição nem o componente; variantes são linhas de tabela (`[Theory]`/`it.each`); acima disso, o porquê na linha do cenário. Nenhum teste que só fixa texto, prompt ou constante, nenhum de DTO/mapping, e o mesmo comportamento numa camada só (handler **ou** endpoint). Teste além do orçamento é escopo para o revisor. O `verify` avisa quando o teste passa da produção.
|
|
76
76
|
|
|
77
77
|
```markdown
|
|
@@ -105,7 +105,7 @@ O `create` já escreveu o esqueleto em `2-plan/plan.md` (leia e preencha; `chore
|
|
|
105
105
|
- Cenário que se observa por um artefato de outra fatia (rota, endpoint, componente que ela cria) faz da fatia que o cobre um `depende:` daquela — senão as duas caem na mesma onda e o teste do cenário não tem por onde provar. O `plan-check` não vê de quem é a rota: a regra é sua.
|
|
106
106
|
|
|
107
107
|
**Contrato de implementação**: o que quem implementa precisa e o código não diz.
|
|
108
|
-
- **Verificação de runtime:** o stack sobe por `morph-spec e2e up {feature}` (compose do projeto; sem compose, isso é uma fatia); os fluxos que o verificador navega, nos verbos dos cenários; base URL `process.env.PLAYWRIGHT_BASE_URL ?? \`http://localhost:${process.env.PORT ?? 3000}\``, compose com `"${PORT:-3000}:3000"` e sem `container_name` (standard `infrastructure/docker/local-compose-isolation`). App com login: `e2e.auth` em `config.json` (`mode: seed|storageState|bypass|none`, `seedCommand`, `storageStatePath`, `envFile`), credenciais num `.env.e2e` gitignored — **`mode` é obrigatório sempre que houver `seedCommand`**, sem ele o seed fica inerte. Biblioteca pura: registre a dispensa e o porquê.
|
|
108
|
+
- **Verificação de runtime:** o stack sobe por `morph-spec e2e up {feature}` (compose do projeto; sem compose, isso é uma fatia); os fluxos que o verificador navega, nos verbos dos cenários; base URL `process.env.PLAYWRIGHT_BASE_URL ?? \`http://localhost:${process.env.PORT ?? 3000}\``, compose com `"${PORT:-3000}:3000"` e sem `container_name` (standard `infrastructure/docker/local-compose-isolation`). App cujo build é caro dentro do Docker sobe como processo do host por `e2e.hostServices` (compose só com as dependências; campos em `morph-spec e2e --help`). App com login: `e2e.auth` em `config.json` (`mode: seed|storageState|bypass|none`, `seedCommand`, `storageStatePath`, `envFile`), credenciais num `.env.e2e` gitignored — **`mode` é obrigatório sempre que houver `seedCommand`**, sem ele o seed fica inerte. Biblioteca pura: registre a dispensa e o porquê.
|
|
109
109
|
- **Baseline é um COMANDO, nunca um número.** O plano congela e é lido por dias: um número cravado está errado na primeira fatia, e quem confia nele "conserta" o que não quebrou. Registre como medir (`npm test`, `dotnet test`); o `expectedCount` sai de `morph-spec verify {feature} --record-baseline`, nunca digitado. Sem `project.stack`/`project.tests` no `config.json`, a `morph-apply` os declara a partir destes comandos antes do primeiro verify.
|
|
110
110
|
- **Pré-requisito é um PREDICADO, nunca um SHA literal:** "`git merge-base --is-ancestor origin/main HEAD` sai 0" — o SHA muda no primeiro squash.
|
|
111
111
|
|
|
@@ -4,7 +4,7 @@ description: Conduz o Gate 3 — despacha verificador ∥ revisor (∥ seguranç
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando a fase review começar, com todas as fatias com relatório final e o verify da feature verde
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -51,7 +51,8 @@ símbolo público do diff, `morph-spec graph affected <símbolo> --prod --json`;
|
|
|
51
51
|
(`no-graph`/`not-found`/`ambiguous`), siga sem o blast radius — o revisor cai para `git grep` — e
|
|
52
52
|
registre a ausência no recado a ele. Cada agente nasce com contexto fresco e **nunca recebe relatório anterior** (ancoragem).
|
|
53
53
|
`chore` tem review leve: só o revisor (∥ segurança, quando exigida) — sem verificador, sem cobertura de
|
|
54
|
-
`Cn`, sem mutação. Despache todos na mesma resposta
|
|
54
|
+
`Cn`, sem mutação. Despache todos na mesma resposta, **sem nome no dispatch**: com nome, o `agent_type` que o hook
|
|
55
|
+
`test-budget` lê vira o nome, e o guard de review não reconhece o papel.
|
|
55
56
|
```
|
|
56
57
|
dispatch Agent (subagent_type: "morph-verifier", model: {reviewModels.verifier.model}) with prompt:
|
|
57
58
|
feature, base, sha, JSON do verify (nós, e2e, URL) se já terminou, 1-design/mockups.md e
|
|
@@ -4,7 +4,7 @@ description: Localiza um standard específico via busca server-side (morph-spec
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando precisar consultar um standard específico durante a implementação
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|
|
@@ -4,7 +4,7 @@ description: Conduz a fase opcional de UI/UX de uma feature com plano aprovado
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando o ramo de design for tomado (morph-spec advance {feature} --design, depois do gate do plano) e antes de /morph-apply
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.7.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-uiux
|
|
@@ -1629,9 +1629,10 @@
|
|
|
1629
1629
|
"portNames": "#project-named-port-variables",
|
|
1630
1630
|
"rules": "#core-rules",
|
|
1631
1631
|
"enforcement": "#what-the-harness-enforces",
|
|
1632
|
+
"hostServices": "#services-on-the-host",
|
|
1632
1633
|
"skeleton": "#minimal-compose-skeleton"
|
|
1633
1634
|
},
|
|
1634
|
-
"digest": "Every worktree runs its OWN compose stack (project morph-{feature}, own postgres, own volumes). Ports come from the worktree block: PORT +0, API_PORT +1 (ASPNETCORE_URLS on .NET), POSTGRES_PORT +2, REDIS_PORT +3 — publish via ${VAR:-default}:container, never a fixed host port, never container_name (not prefixed by the project → collides). Stable POSTGRES_DB, healthcheck on every service, DATABASE_URL with an in-network default, playwright.config reads PLAYWRIGHT_BASE_URL. A non-isolable compose fails the verify e2e node inside a worktree. finish
|
|
1635
|
+
"digest": "Every worktree runs its OWN compose stack (project morph-{feature}, own postgres, own volumes). Ports come from the worktree block: PORT +0, API_PORT +1 (ASPNETCORE_URLS on .NET), POSTGRES_PORT +2, REDIS_PORT +3 — publish via ${VAR:-default}:container, never a fixed host port, never container_name (not prefixed by the project → collides). Stable POSTGRES_DB, healthcheck on every service, DATABASE_URL with an in-network default, playwright.config reads PLAYWRIGHT_BASE_URL. A non-isolable compose fails the verify e2e node inside a worktree. finish/delete tear down with --rmi local -v (--keep-volumes drops -v on finish); worktree remove of a morph-task/* branch with -v, of morph/{f} without. An app whose build is expensive in the Docker VM runs as a host process via e2e.hostServices (compose keeps only the dependencies; ports from the block). Cloud DB credentials go in e2e.auth.envFile, never in .morph/worktree.env. A compose that reads a port by ANOTHER name (PG_PORT) is not fed by the block: declare it in config.json e2e.portEnvNames ({\"postgres\": \"PG_PORT\"}) and the harness exports it with the same value in worktree.env, the compose env and the hook."
|
|
1635
1636
|
},
|
|
1636
1637
|
{
|
|
1637
1638
|
"id": "infrastructure-neon-pgrag",
|
|
@@ -51,10 +51,23 @@ Keys are `web`, `api`, `postgres` and `redis`. Each declared name carries the **
|
|
|
51
51
|
## What the harness enforces
|
|
52
52
|
|
|
53
53
|
- Inside a secondary worktree, a compose with a fixed host port or a `container_name` **fails the `verify` e2e node** (`compose-not-isolable`) — Gate 3 cannot pass on a stack that cannot run beside its siblings. At the primary root the same finding is a warning.
|
|
54
|
-
- `morph-spec finish`
|
|
54
|
+
- `morph-spec finish` and `morph-spec delete` tear the stack down with `down --remove-orphans --rmi local -v` (`--keep-volumes` on `finish` drops the `-v`) and delete the freshness stamp. `--rmi local` removes only the images compose built, never a pulled one (`postgres`) that the next feature would download again; `docker builder prune` never runs, because the build cache is global to the machine. `morph-spec worktree remove` (and `worktree reap`, which goes through it) tears down a `morph/{f}` branch without volumes (the feature is still open) and a `morph-task/*` branch with `-v` (the task is disposable). Every one of these also stops the `e2e.hostServices` processes.
|
|
55
55
|
- Two worktrees never share `POSTGRES_PORT`: the block is allocated first-free-index per feature and released only when the worktree is removed.
|
|
56
56
|
- The credentials env file is read **after** the block but cannot outrank it. All four surfaces (`e2e up`, the `verify` e2e node, the teardown, `morph-spec env`) build their environment through the same `buildComposeEnv`, which drops the layout keys coming from that file and names them on **stderr** — stdout stays eval-safe for `eval "$(morph-spec env)"`. With no block allocated (outside a worktree) nothing is filtered: there the file is the only port source.
|
|
57
57
|
|
|
58
|
+
## Services on the host
|
|
59
|
+
|
|
60
|
+
When building an app inside the Docker VM is expensive (a `dotnet publish` or `next build` repeating the build the `verify` already did on the host), run it as a host process through `config.json → e2e.hostServices` and keep only the dependencies (postgres, redis) in compose. The app listens on the ports of the worktree block — `${API_PORT}` / `${PORT}` in its `env` and `health` — so two worktrees still never collide.
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
"e2e": { "hostServices": [
|
|
64
|
+
{ "name": "web", "build": "npm run build", "start": "node .next/standalone/server.js",
|
|
65
|
+
"env": { "PORT": "${PORT}" }, "health": "http://localhost:${PORT}/" }
|
|
66
|
+
] }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`e2e up` and the `verify` e2e node run `compose up --wait`, then each host service in array order (build → start → health), then the auth seed. They stay up until `e2e down`, `finish`, `delete` or `worktree remove`. Fields, reuse and failure rules: `morph-spec e2e --help` and the `e2e` section of `.morph/framework/CLI.md`.
|
|
70
|
+
|
|
58
71
|
## Minimal compose skeleton
|
|
59
72
|
|
|
60
73
|
```yaml
|
|
@@ -96,5 +109,6 @@ volumes:
|
|
|
96
109
|
```bash
|
|
97
110
|
# inside the worktree, outside Claude Code
|
|
98
111
|
eval "$(morph-spec env)" # PowerShell: morph-spec env --powershell | Invoke-Expression
|
|
112
|
+
# --with-secrets also loads the e2e.auth.envFile credentials
|
|
99
113
|
docker compose up -d --wait # lands in project morph-{feature}, on the block's ports
|
|
100
114
|
```
|