@polymorphism-tech/morph-spec 9.7.0 → 9.8.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/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/bin/morph-spec.js +1 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +1 -1
- package/framework/docs/CLI.md +18 -9
- package/framework/evals/review-checklist.md +3 -3
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/heavy-slots.js +41 -4
- package/framework/schemas/config.schema.json +1 -1
- 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 +2 -2
- 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 +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- 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/project/worktree.js +24 -2
- package/src/commands/state/finish.js +43 -1
- package/src/commands/state/report-check.js +36 -1
- 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/heavy/gate.js +22 -1
- package/src/lib/review-reports.js +5 -3
- package/src/lib/tasks/test-nodes.js +24 -6
- package/src/lib/tasks/test-runner.js +40 -1
- package/src/lib/validators/verify-runner.js +50 -12
- package/src/utils/agents-installer.js +1 -1
package/CLAUDE.md
CHANGED
|
@@ -13,7 +13,7 @@ This repo is the **framework source code** — not a project that uses morph-spe
|
|
|
13
13
|
```bash
|
|
14
14
|
# Once per clone: the dashboard render smoke test (test/dashboard/pages-smoke.test.js)
|
|
15
15
|
# needs the dashboard's own deps and FAILS without them — CI installs them too
|
|
16
|
-
|
|
16
|
+
cd src/dashboard && npm install
|
|
17
17
|
|
|
18
18
|
# Dev loop: only the test files the current change can reach (vs the merge-base with main)
|
|
19
19
|
npm run test:changed # add -- --list to see the selection and why
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
|
|
4
4
|
|
|
5
5
|
**Package:** `@polymorphism-tech/morph-spec`
|
|
6
|
-
**Version:** 9.
|
|
6
|
+
**Version:** 9.8.0
|
|
7
7
|
**Requires:** Node.js 22+, Claude Code
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -380,4 +380,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
|
|
|
380
380
|
|
|
381
381
|
---
|
|
382
382
|
|
|
383
|
-
*morph-spec v9.
|
|
383
|
+
*morph-spec v9.8.0 by [Polymorphism Tech](https://polymorphism.tech)*
|
package/bin/morph-spec.js
CHANGED
|
@@ -344,7 +344,7 @@ program
|
|
|
344
344
|
.option('--filter <ids>', 'Run only the test nodes whose id matches (comma-separated; exact or substring). Unmatched nodes are reported as skip; matching nothing is a usage error (exit 2)')
|
|
345
345
|
.option('--linux', 'Run the test nodes inside a Linux container, on a COPY of the tree (the host tree is mounted read-only and copied in — a bind mount is case-insensitive and cannot reproduce case defects). No Docker = usage error, never a skip')
|
|
346
346
|
.option('--linux-image <img>', 'Image used by --linux (default: mcr.microsoft.com/dotnet/sdk:10.0)')
|
|
347
|
-
.option('--record-baseline', 'Measure every test node and write expectedCount back into .morph/config/config.json (only from an all-green run; never creates config.json)')
|
|
347
|
+
.option('--record-baseline', 'Measure every test node and write expectedCount of the node-test/evals nodes back into .morph/config/config.json, removing it from the other runners (only from an all-green run; never creates config.json)')
|
|
348
348
|
.option('--refresh-cache', 'Rewrite the response cache of every `runner: "evals"` node against the real provider. NEVER implicit: a cache rewrite is a paid call nobody asked for. Requires the declared envFile; no eval node declared, --skip-tests, or a --filter that excludes them all are usage errors (exit 2), refused BEFORE the cache is touched')
|
|
349
349
|
.option('--skip-tests', 'Skip the test node. The run is partial, so the verification stamp is NOT written (reason "skip-tests")')
|
|
350
350
|
.option('--skip-build', 'Skip the build node. The run is partial, so the verification stamp is NOT written (reason "skip-build")')
|
package/claude-plugin.json
CHANGED
package/docs/CHEATSHEET.md
CHANGED
package/docs/QUICKSTART.md
CHANGED
package/framework/agents.json
CHANGED
|
@@ -398,7 +398,7 @@
|
|
|
398
398
|
],
|
|
399
399
|
"model": "sonnet",
|
|
400
400
|
"maxTurns": 60,
|
|
401
|
-
"persona": "Você é o revisor independente do Gate 3, read-only. Leia como o arquiteto que herda este repo amanhã: simples, legível, fácil de estender. Não participou da implementação e não prova cenários (é o verificador). Não roda teste nem `verify` (o hook `test-budget` nega). Você lê SÓ o commit revisado: `git diff {base}..{sha}`, `git show {sha}:<caminho>`, `git log {base}..{sha}` — nunca a árvore de trabalho (Read/Glob/Grep sobre `src/` leem o disco, onde o verificador pode estar com um mutante aplicado). `base` e `sha` vêm no dispatch. Julga o diff em cinco dimensões, na seção \"Revisor\" de `.morph/framework/evals/review-checklist.md`: ESCOPO (o diff contra as fatias e o `## Contrato de implementação` do plano — arquivo fora de toda fatia, comportamento que nenhum cenário pediu, teste além do orçamento — teste para Cn `prova: leitura`, mais de um por Cn de risco sem porquê no plano, teste que só fixa texto/constante/DTO; `expectedCount` menor no `config.json` sem teste apagado no mesmo diff é `regressao`); ARQUITETURA (padrão ou abstração nova contra o stack do projeto e os standards — camada que o projeto não usa, repositório sobre o DbContext, dependência nova sem decisão no plano — e o que o DIFF introduziu da lista fechada `architecture-engineering-lens#review-blockers`; o que já existia no arquivo é nota); REGRESSÃO (para cada símbolo público alterado, `morph-spec graph affected <símbolo> --prod --json`; sem grafo — `no-graph`, `not-found`, `ambiguous` — caia para `git grep -n <símbolo> {sha}` e siga, a ausência de grafo nunca bloqueia; ausência de consumidor nunca sai do grafo); O QUE A CORREÇÃO ABRIU (revisando uma correção de rodada anterior, releia o comportamento vizinho); PERFORMANCE só com evidência (lista sem paginação no servidor sobre volume real, consulta N+1 visível no diff) e RASTREABILIDADE de chamada a LLM (dá para reconstruir o que foi ao provedor e o que voltou?). Tipos de bloqueador: `escopo`, `arquitetura`, `regressao`, `performance`. Seu default é SHIP: dúvida vira nota. Arquivo do framework ou da feature (o checklist, o plan.md) você lê com Read, nunca `sed`/`cat`/`head`; código do commit, com `git show {sha}:<caminho>`. NUNCA modifique código, plan.md, checks.json ou feature.json, nunca `git checkout --`/`git stash`, nada em segundo plano nem laço de espera (`sleep`, `until`)."
|
|
401
|
+
"persona": "Você é o revisor independente do Gate 3, read-only. Leia como o arquiteto que herda este repo amanhã: simples, legível, fácil de estender. Não participou da implementação e não prova cenários (é o verificador). Não roda teste nem `verify` (o hook `test-budget` nega). Você lê SÓ o commit revisado: `git diff {base}..{sha}`, `git show {sha}:<caminho>`, `git log {base}..{sha}` — nunca a árvore de trabalho (Read/Glob/Grep sobre `src/` leem o disco, onde o verificador pode estar com um mutante aplicado). `base` e `sha` vêm no dispatch. Julga o diff em cinco dimensões, na seção \"Revisor\" de `.morph/framework/evals/review-checklist.md`: ESCOPO (o diff contra as fatias e o `## Contrato de implementação` do plano — arquivo fora de toda fatia, comportamento que nenhum cenário pediu, teste além do orçamento — teste para Cn `prova: leitura`, mais de um por Cn de risco sem porquê no plano, teste que só fixa texto/constante/DTO; teste apagado sem porquê no relatório da fatia, edição de `.sln`/`.csproj`/glob do runner que tira testes da execução, ou `expectedCount` menor no `config.json` sem teste apagado no mesmo diff, é `regressao`); ARQUITETURA (padrão ou abstração nova contra o stack do projeto e os standards — camada que o projeto não usa, repositório sobre o DbContext, dependência nova sem decisão no plano — e o que o DIFF introduziu da lista fechada `architecture-engineering-lens#review-blockers`; o que já existia no arquivo é nota); REGRESSÃO (para cada símbolo público alterado, `morph-spec graph affected <símbolo> --prod --json`; sem grafo — `no-graph`, `not-found`, `ambiguous` — caia para `git grep -n <símbolo> {sha}` e siga, a ausência de grafo nunca bloqueia; ausência de consumidor nunca sai do grafo); O QUE A CORREÇÃO ABRIU (revisando uma correção de rodada anterior, releia o comportamento vizinho); PERFORMANCE só com evidência (lista sem paginação no servidor sobre volume real, consulta N+1 visível no diff) e RASTREABILIDADE de chamada a LLM (dá para reconstruir o que foi ao provedor e o que voltou?). Tipos de bloqueador: `escopo`, `arquitetura`, `regressao`, `performance`. Seu default é SHIP: dúvida vira nota. Arquivo do framework ou da feature (o checklist, o plan.md) você lê com Read, nunca `sed`/`cat`/`head`; código do commit, com `git show {sha}:<caminho>`. NUNCA modifique código, plan.md, checks.json ou feature.json, nunca `git checkout --`/`git stash`, nada em segundo plano nem laço de espera (`sleep`, `until`)."
|
|
402
402
|
},
|
|
403
403
|
"security": {
|
|
404
404
|
"title": "Gate 3 Security Reviewer",
|
package/framework/docs/CLI.md
CHANGED
|
@@ -290,13 +290,15 @@ As specs são procuradas na raiz, em `project.frontendPath` e, sem ele, nas past
|
|
|
290
290
|
|
|
291
291
|
**Dispensa legítima × setup faltando:** projeto sem compose e sem a integração `docker` é `skip` com razão; projeto que **declara** `docker` e não tem compose é `fail` — é verificação que nunca foi montada, não projeto que não containeriza.
|
|
292
292
|
|
|
293
|
-
**Descoberta de testes e relógio:** a varredura por `*Tests.csproj` **não enxerga worktrees** — nem os convencionais (`worktrees/`, `.worktrees/`) nem os registrados em `git worktree list` fora da convenção. O timeout do nó `tests` deixou de ser fixo: `--timeout` (que agora cobre build **e** tests) > `project.testTimeoutMs` no `config.json` > **
|
|
293
|
+
**Descoberta de testes e relógio:** a varredura por `*Tests.csproj` **não enxerga worktrees** — nem os convencionais (`worktrees/`, `.worktrees/`) nem os registrados em `git worktree list` fora da convenção. O timeout do nó `tests` deixou de ser fixo: `--timeout` (que agora cobre build **e** tests) > `timeoutMs` do nó > `project.testTimeoutMs` no `config.json` > **30 min por projeto de teste encadeado** (o build também tem teto de 30 min) — uma suíte longa nunca nasce condenada pelo relógio. Quando o relógio estoura, o nó diz isso com nome (`timeout após N ms na execução X de Y — nenhum teste falhou`) em vez de devolver log truncado.
|
|
294
|
+
|
|
295
|
+
**Falha nomeada:** num nó de testes vermelho, o `feedback[]` traz primeiro `[tests/{id}] falhou: <teste>` (até 10, lidas da saída **inteira**: `not ok` do TAP do `node --test` — só as folhas, a suíte-mãe sai —, `FAIL`/`×` do vitest, `failed`/`Failed`/`com falha` do dotnet), depois `[tests/{id}] log completo: .morph/logs/verify-tests-{id}.log` e só então a cauda de 20 linhas; o `reason` do nó resume as três primeiras. Runner que nenhuma forma reconhece fica só com o caminho do log e a cauda. O log é gravado a cada run vermelho (e no `--linux`); erro de escrita não muda o veredito.
|
|
294
296
|
|
|
295
297
|
**`--fresh-e2e` é destrutivo por definição:** derruba a stack anterior **com os volumes nomeados** (`down -v`), apaga o carimbo de frescor, sobe com `--build`, reconstrói e reinicia os `e2e.hostServices` e força o seed de auth a rodar de novo.
|
|
296
298
|
|
|
297
299
|
**`--filter <ids>` escolhe os nós** (lista separada por vírgula, casando `id` exato ou substring). O Gate 3 é `verify {f} --json` **sem filtro**, porque a prova que o verificador lê tem de cobrir todos os nós (o CI idem) — e um `verify {f} --filter …` não persiste o stamp (`persisted: false`, `reason: 'filtered'`), nem um com `--skip-tests` (`reason: 'skip-tests'`) ou `--skip-build` (`reason: 'skip-build'`): um nó pulado não reprova o rollup, e rodada parcial nunca fica no lugar da prova do Gate 3. O nó não casado **aparece** em `runs[]` como `skip` nomeando o filtro, em vez de sumir — suíte que some do relatório é verde sobre código não testado. E `--filter` que não casa nenhum nó é **erro de uso (exit 2)**, checado antes do build: filtrar tudo e reportar verde seria a forma mais barata de recriar o defeito que a lista de nós existe para matar.
|
|
298
300
|
|
|
299
|
-
**`--record-baseline` é a ÚNICA via de escrita da baseline:** mede cada nó numa execução real e grava `project.tests[].expectedCount` de volta no `config.json` (escrita atômica `tmp.{pid}`+rename, preservando todo o resto do arquivo). Recusa alto e claro em vez de improvisar: execução **vermelha** não grava (congelaria a regressão), execução **parcial** não grava (com `--filter` alguns nós ficam sem medida, e baseline de meia frota é afirmação sobre a outra metade que ninguém fez), contagem **desconhecida** não grava (`null` nunca vira `0`), e **sem `config.json` é erro de uso (exit 2)** — o arquivo nunca é criado implicitamente. A execução que gravou a baseline persiste o stamp; a que não gravou nada, não.
|
|
301
|
+
**`--record-baseline` é a ÚNICA via de escrita da baseline:** mede cada nó numa execução real e grava `project.tests[].expectedCount` dos nós `node-test`/`evals` de volta no `config.json` (e apaga o dos demais) (escrita atômica `tmp.{pid}`+rename, preservando todo o resto do arquivo). Recusa alto e claro em vez de improvisar: execução **vermelha** não grava (congelaria a regressão), execução **parcial** não grava (com `--filter` alguns nós ficam sem medida, e baseline de meia frota é afirmação sobre a outra metade que ninguém fez), contagem **desconhecida** não grava (`null` nunca vira `0`), e **sem `config.json` é erro de uso (exit 2)** — o arquivo nunca é criado implicitamente. A execução que gravou a baseline persiste o stamp; a que não gravou nada, não.
|
|
300
302
|
|
|
301
303
|
**`--linux` roda as suítes dentro de um container Linux** (`mcr.microsoft.com/dotnet/sdk:10.0` por default; sobrescrevível por `--linux-image` ou por `project.tests[].linuxImage`), e a árvore sob teste é **copiada** para o filesystem do container, não montada: o bind mount do Docker Desktop no Windows atravessa 9p/gRPC-FUSE, que é **case-insensitive** — rodar em cima do mount devolveria o mesmo verde do Windows e não provaria nada sobre defeitos de caixa (`Link=`, `Uri.TryCreate`), que são exatamente os que só aparecem no Linux. A raiz é montada **read-only** em `/src` só como origem do `tar` (com exclusão de `node_modules`, `bin`, `obj`, `.git` e os diretórios-contêiner de worktree), a cópia vive em `/work`, e cada nó roda de `/work/{cwd}`. Sem Docker, **erro de uso (exit 2)**, jamais um `skip`: "não deu para checar no Linux" lido como verde é o próprio defeito. `--linux` com `--skip-tests` se anulam (erro de uso), e um nó `e2e: true` é pulado no container com motivo (docker-em-docker fora de escopo). O resultado marca `nodes.tests.environment = { kind: 'linux-container', image }` — sem isso, um verde de container e um verde de host viram indistinguíveis no `feature.json` e a próxima pessoa não sabe o que foi provado.
|
|
302
304
|
|
|
@@ -409,6 +411,8 @@ Isolamento entre worktrees: `COMPOSE_PROJECT_NAME=morph-{feature}` separa os con
|
|
|
409
411
|
|
|
410
412
|
**Espera:** slot cheio, ou RAM livre (`os.freemem()`) abaixo do piso do tipo do comando, espera até `heavy.waitTimeoutSec` (600) avisando em **stderr** a cada 30 s e nomeando quem segura (o `--json` mantém o stdout limpo).
|
|
411
413
|
|
|
414
|
+
**Uma suíte de `verify` por vez:** o nó de testes de um `verify` espera também enquanto o `verify` de outra feature está no nó de testes, segurando o slot que já tinha; `heavy run` (o filtro de sub-agente), build, e2e e `mutate` não esperam por isso, só pelos slots. Estouro da espera sai `reason: "suite"` (exit 3).
|
|
415
|
+
|
|
412
416
|
**Piso de RAM por tipo** (`heavy.minFreeMemGbByKind`, padrão `unit` 1,5 · `build` 4 · `e2e` 4 GB): `unit` = vitest/jest, `npm|pnpm|yarn test`, `npm run test[:x]` e o nó `tests` do verify; `e2e` = playwright, `docker compose up`, script com `e2e` no nome, o nó `e2e` e o `e2e up`; `build` = o resto (`dotnet build|test|publish` — o `dotnet test` compila —, `next build`, `docker build`, `mutate` e comando desconhecido). O `heavy run` e o hook do `gateBash` classificam pelo comando; o `verify` toma o slot com o piso do primeiro nó e, antes de um nó mais pesado (tests → e2e), espera de novo só pela RAM segurando o slot — estourar ali também é exit 3, sem stamp. `heavy.minFreeMemGb` (4) virou o fallback: **definido explicitamente**, vale para todo tipo que `minFreeMemGbByKind` não lista (`0` continua desligando a checagem em tudo); sem ele, cada tipo usa o seu padrão.
|
|
413
417
|
|
|
414
418
|
**Ocupado por RAM** (`verify`, `e2e up`, `heavy run`): a mensagem nomeia o piso e a chave (`heavy.minFreeMemGbByKind.<tipo>`) e lista as stacks e2e de pé deste repo (`docker compose ls`, prefixo `morph-`, compose sob a raiz principal — de todas as worktrees). Feature já arquivada (`.morph/archive/<f>` sem `.morph/features/<f>`) vem com `morph-spec e2e down <f>`; stack sem feature, com `docker compose -p <nome> down`. Docker fora ou lento (10 s) = lista vazia, nunca erro.
|
|
@@ -550,9 +554,9 @@ em runtime é `validateTestNodes()` — schema que ninguém chama é decoração
|
|
|
550
554
|
"tests": [
|
|
551
555
|
{ "id": "dotnet", "runner": "dotnet", "cwd": ".",
|
|
552
556
|
"command": "dotnet test tests/App.Tests/App.Tests.csproj --nologo -v q --no-build",
|
|
553
|
-
"build": ["tests/App.Tests/App.Tests.csproj"], "
|
|
557
|
+
"build": ["tests/App.Tests/App.Tests.csproj"], "timeoutMs": 600000 },
|
|
554
558
|
{ "id": "vitest", "runner": "vitest", "cwd": "catalog-admin",
|
|
555
|
-
"command": "npx vitest run --reporter=default"
|
|
559
|
+
"command": "npx vitest run --reporter=default" },
|
|
556
560
|
{ "id": "api-integration", "runner": "dotnet", "cwd": ".",
|
|
557
561
|
"command": "dotnet test tests/App.Integration.Tests/App.Integration.Tests.csproj -v q", "e2e": true }
|
|
558
562
|
]
|
|
@@ -566,8 +570,8 @@ em runtime é `validateTestNodes()` — schema que ninguém chama é decoração
|
|
|
566
570
|
| `command` | **sim** | Comando literal, executado por shell a partir de `cwd`. Nunca é reescrito pelo verify |
|
|
567
571
|
| `cwd` | não (`"."`) | Relativo à raiz. Path que resolve **fora** da raiz é recusado (endereça outra árvore); `cwd` inexistente é `fail`, não `skip` — um skip esconderia erro de plano atrás de gate verde |
|
|
568
572
|
| `runner` | não (inferido) | `dotnet` \| `vitest` \| `jest` \| `node-test` \| `playwright` \| `none` \| `evals`. Escolhe o extrator de contagem e se há prebuild. `none` = contagem DESCONHECIDA, nunca zero. `evals` é o único que **nunca é inferido** e o único que exige um bloco próprio — ver a subseção abaixo |
|
|
569
|
-
| `expectedCount` | não | Baseline. **Gravada só por `verify --record-baseline`**, nunca digitada. Ausente = `null` (sem baseline); `0` **declarado** é uma baseline real |
|
|
570
|
-
| `timeoutMs` | não | Teto deste nó.
|
|
573
|
+
| `expectedCount` | não | Baseline, só nos runners de contagem estável (`node-test`, `evals`); nos outros é ignorada e o `--record-baseline` a apaga. **Gravada só por `verify --record-baseline`**, nunca digitada. Ausente = `null` (sem baseline); `0` **declarado** é uma baseline real |
|
|
574
|
+
| `timeoutMs` | não | Teto deste nó. Sem ele vale `project.testTimeoutMs` e, por fim, o default de 30 min × projetos encadeados |
|
|
571
575
|
| `build` | não | Projetos a compilar antes (só `runner: "dotnet"`). O `mutate` não o exige: sem ele, descobre o alvo pelo comando. Declarado em nó não-dotnet é erro de config, não campo silenciosamente ignorado |
|
|
572
576
|
| `e2e` | não (`false`) | Exige a stack de pé. No host roda **depois** do nó `e2e`, contra a stack que ele subiu e com o mesmo env do compose (portas, `PLAYWRIGHT_BASE_URL`, `COMPOSE_PROJECT_NAME`); stack que não subiu (`e2e` em `skip`/`fail`) pula o nó com o motivo e um aviso `[tests/{id}] warning:`. Pulado **com motivo** dentro do `--linux`. Playwright não entra aqui: é o próprio nó `e2e` (detectado ou `e2e.command`), e declará-lo também como nó de teste rodaria a suíte duas vezes |
|
|
573
577
|
| `linuxImage` | não | Imagem do container deste nó sob `--linux` |
|
|
@@ -704,6 +708,11 @@ reprova** (mesmo com exit 0 — é regressão silenciosa), **alta apenas avisa**
|
|
|
704
708
|
— um zero fabricaria queda a partir de "não deu para ler o output". O aviso de alta sai **mesmo com o
|
|
705
709
|
nó verde**, que é o único lugar onde ele pode existir.
|
|
706
710
|
|
|
711
|
+
**Baseline só onde a contagem fica parada.** `dotnet`, `vitest`, `jest` e `playwright` crescem a cada
|
|
712
|
+
fatia: a baseline deles só avisava a alta ou barrava a limpeza deliberada, e teste apagado já aparece no
|
|
713
|
+
diff que o Gate 3 lê. Nesses runners não há baseline; vale o **piso zero** — nó verde que executou `0`
|
|
714
|
+
testes reprova com `suíte verde sem nenhum teste executado` (suíte que parou de rodar).
|
|
715
|
+
|
|
707
716
|
**Prebuild e `--no-build` (por que um `dotnet test` verde podia mentir).** Num nó `runner: "dotnet"`,
|
|
708
717
|
os alvos de `build[]` — ou, na falta, os `.csproj` extraídos do próprio `command` — são compilados
|
|
709
718
|
antes da execução, com `dotnet build "<alvo>" --nologo -v q`. Compilação que falha reprova o nó com
|
|
@@ -716,10 +725,10 @@ a partir do binário do último build bom e reportava verde. `--no-build` contin
|
|
|
716
725
|
sem `--no-build` já compila sozinho, e pré-compilar ali só dobraria o relógio do caminho quente.
|
|
717
726
|
|
|
718
727
|
**O ciclo da baseline, e o número que só sai à mão.** `verify --record-baseline` é a única via de
|
|
719
|
-
escrita: mede cada nó numa execução real e grava `expectedCount` de volta no `config.json` (escrita
|
|
728
|
+
escrita: mede cada nó numa execução real e grava `expectedCount` dos nós `node-test`/`evals` de volta no `config.json` (apaga o dos demais, listados como `removed`; escrita
|
|
720
729
|
atômica `tmp.{pid}` + rename, preservando todo o resto do arquivo). Recusa **quatro** vezes — execução
|
|
721
730
|
vermelha (fora o `count-drop` com exit 0, abaixo), execução **parcial** (sob `--filter`, mesmo com o verify verde: baseline de meia frota é
|
|
722
|
-
afirmação sobre a outra metade que ninguém fez), contagem desconhecida
|
|
731
|
+
afirmação sobre a outra metade que ninguém fez), contagem desconhecida num nó `node-test`/`evals`, e **sem `config.json` ou sem
|
|
723
732
|
`project.tests[]`** (erro de uso, exit 2; o arquivo nunca é criado implicitamente — gravar os nós que o
|
|
724
733
|
fallback sintetiza exigiria inventar a declaração que o comando veio manter). Verde com gravação
|
|
725
734
|
recusada sai **exit 1**, com a razão em **stderr** (sobrevive ao `--json`). A gravação é governada
|
|
@@ -739,7 +748,7 @@ nomeia o nó que impediu. **Apagar teste é legítimo:** a única cor vermelha q
|
|
|
739
748
|
|
|
740
749
|
> **Não existe constante de 600 s neste CLI** (`grep -rn "600000" src/ framework/` → zero). O teto que
|
|
741
750
|
> corta uma suíte longa é o do **Bash do harness**, externo ao morph-spec, e não há o que "subir". Os
|
|
742
|
-
> relógios que o CLI de fato tem: build **
|
|
751
|
+
> relógios que o CLI de fato tem: build **1 800 000 ms** (30 min), tests **1 800 000 ms por projeto de teste
|
|
743
752
|
> encadeado**, health do e2e **180 000 ms**, specs Playwright **300 000 ms**, mais `timeoutMs` por nó,
|
|
744
753
|
> que substitui o default do `tests` quando ele não serve. `--timeout <ms>` cobre build **e** tests.
|
|
745
754
|
> **A suíte inteira de um projeto grande não cabe numa invocação de Bash do harness:** rode
|
|
@@ -116,9 +116,9 @@ Read-only, sobre o commit do dispatch: `git diff {base}..{sha}`, `git show {sha}
|
|
|
116
116
|
1. **Escopo**: o diff contra as fatias (`arquivos:`) e o `## Contrato de implementação` do plano.
|
|
117
117
|
Teste além do orçamento também é escopo: teste para Cn `prova: leitura`, mais de um teste por Cn de
|
|
118
118
|
risco sem porquê no plano, teste que só fixa texto/constante/DTO, o mesmo comportamento em duas
|
|
119
|
-
camadas — bloqueador `escopo` (apague, não acrescente).
|
|
120
|
-
`.morph/config/config.json` do diff sem teste apagado no mesmo
|
|
121
|
-
suíte que parou de rodar, não limpeza.
|
|
119
|
+
camadas — bloqueador `escopo` (apague, não acrescente). Teste apagado sem porquê no relatório da
|
|
120
|
+
fatia, edição de `.sln`/`.csproj`/glob do runner que tira testes da execução, ou `expectedCount` menor no `.morph/config/config.json` do diff sem teste apagado no mesmo
|
|
121
|
+
diff, é bloqueador `regressao`: é suíte que parou de rodar, não limpeza.
|
|
122
122
|
2. **Arquitetura**: padrão ou abstração nova contra o stack do projeto e os standards (camada que o
|
|
123
123
|
projeto não usa, repositório sobre o DbContext, dependência nova sem decisão no plano).
|
|
124
124
|
3. **Regressão**: para cada símbolo público alterado, `morph-spec graph affected <símbolo> --prod --json`.
|
|
Binary file
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# morph-spec statusline — installed globally to ~/.claude/statusline.sh
|
|
3
|
-
# Claude Code invokes this with JSON via stdin after each response.
|
|
4
|
-
# Requires: Python 3 available as `python3` or `python` on PATH.
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
-
if command -v python3 &>/dev/null; then
|
|
8
|
-
python3 "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
9
|
-
elif command -v python &>/dev/null; then
|
|
10
|
-
python "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
11
|
-
fi
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# morph-spec statusline — installed globally to ~/.claude/statusline.sh
|
|
3
|
+
# Claude Code invokes this with JSON via stdin after each response.
|
|
4
|
+
# Requires: Python 3 available as `python3` or `python` on PATH.
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
if command -v python3 &>/dev/null; then
|
|
8
|
+
python3 "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
9
|
+
elif command -v python &>/dev/null; then
|
|
10
|
+
python "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
11
|
+
fi
|
|
@@ -51,8 +51,14 @@ function liveHoldersPruningStale(dir, ctx) {
|
|
|
51
51
|
|
|
52
52
|
const kindSuffix = (kind) => (kind ? ` (${kind})` : '');
|
|
53
53
|
|
|
54
|
+
// Two full verify suites never run together; a sub-agent filter or a build only takes a slot.
|
|
55
|
+
export const isVerifySuite = ({ label, node }) => label === 'verify' && node === 'tests';
|
|
56
|
+
|
|
57
|
+
const runningSuite = (holders, ownPath = null) => holders.find((h) => h.path !== ownPath && isVerifySuite(h));
|
|
58
|
+
|
|
54
59
|
function blockedText(blocked, slots, now) {
|
|
55
60
|
const holders = blocked.holders.map((h) => describeHolder(h, now)).join('; ');
|
|
61
|
+
if (blocked.reason === 'suite') return `aguardando o verify de outra feature terminar a suíte de testes (${describeHolder(blocked.holders[0], now)})`;
|
|
56
62
|
if (blocked.reason === 'exclusive') return `aguardando limpeza do servidor de build (${describeHolder(blocked.exclusive, now)})`;
|
|
57
63
|
if (blocked.reason === 'memory') {
|
|
58
64
|
const cause = blocked.holders.length
|
|
@@ -65,13 +71,16 @@ function blockedText(blocked, slots, now) {
|
|
|
65
71
|
|
|
66
72
|
function tryClaim(dir, opts, ctx) {
|
|
67
73
|
const {
|
|
68
|
-
slots, label, feature, node, cwd, minFreeMemGb, freeMem, kind, lease: leaseSpec,
|
|
74
|
+
slots, label, feature, node, cwd, minFreeMemGb, freeMem, kind, suite, lease: leaseSpec,
|
|
69
75
|
} = opts;
|
|
70
76
|
const guarded = withClaimMutex(dir, () => {
|
|
71
77
|
const holders = liveHoldersPruningStale(dir, ctx);
|
|
72
78
|
const exclusive = readExclusive(dir, ctx);
|
|
73
79
|
if (exclusive) return { blocked: { reason: 'exclusive', holders, exclusive } };
|
|
80
|
+
// A full queue is the deeper reason: with no free slot, waiting out the other suite is not enough.
|
|
74
81
|
if (holders.length >= slots) return { blocked: { reason: 'slots', holders } };
|
|
82
|
+
const running = suite ? runningSuite(holders) : null;
|
|
83
|
+
if (running) return { blocked: { reason: 'suite', holders: [running] } };
|
|
75
84
|
const freeGb = freeMem() / GB;
|
|
76
85
|
if (minFreeMemGb > 0 && freeGb < minFreeMemGb) return { blocked: { reason: 'memory', holders, freeGb, minFreeMemGb, kind } };
|
|
77
86
|
|
|
@@ -116,10 +125,18 @@ export function acquireHeavy(opts) {
|
|
|
116
125
|
return { status: 'acquired', lease: null, waitedMs: 0, reentrant: true };
|
|
117
126
|
}
|
|
118
127
|
|
|
128
|
+
return pollClaim(() => tryClaim(dir, claimOpts, ctx), {
|
|
129
|
+
...opts, now, sleep, log, minFreeMemGb: claimOpts.minFreeMemGb,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function pollClaim(claim, {
|
|
134
|
+
slots, waitTimeoutMs, minFreeMemGb = 0, kind = null, now, sleep, log,
|
|
135
|
+
}) {
|
|
119
136
|
const start = now();
|
|
120
137
|
let lastReport = -Infinity;
|
|
121
138
|
for (;;) {
|
|
122
|
-
const attempt =
|
|
139
|
+
const attempt = claim();
|
|
123
140
|
if (attempt.lease) return { status: 'acquired', lease: attempt.lease, waitedMs: now() - start };
|
|
124
141
|
|
|
125
142
|
const waited = now() - start;
|
|
@@ -130,8 +147,8 @@ export function acquireHeavy(opts) {
|
|
|
130
147
|
reason: blocked.reason,
|
|
131
148
|
holders: blocked.holders,
|
|
132
149
|
freeGb: blocked.freeGb ?? null,
|
|
133
|
-
minFreeMemGb
|
|
134
|
-
kind
|
|
150
|
+
minFreeMemGb,
|
|
151
|
+
kind,
|
|
135
152
|
waitedMs: waited,
|
|
136
153
|
};
|
|
137
154
|
}
|
|
@@ -143,6 +160,25 @@ export function acquireHeavy(opts) {
|
|
|
143
160
|
}
|
|
144
161
|
}
|
|
145
162
|
|
|
163
|
+
// Keeps the slot held while it waits: releasing it would let a build or filter take the place.
|
|
164
|
+
export function enterVerifySuite(opts) {
|
|
165
|
+
const {
|
|
166
|
+
dir, lease, maxHoldMs,
|
|
167
|
+
now = Date.now, sleep = sleepSync, isAlive = pidAlive, log = (m) => process.stderr.write(`${m}\n`),
|
|
168
|
+
} = opts;
|
|
169
|
+
const ctx = { now, isAlive, maxHoldMs };
|
|
170
|
+
const claim = () => {
|
|
171
|
+
const guarded = withClaimMutex(dir, () => {
|
|
172
|
+
const running = runningSuite(scanHolders(dir, ctx).live, lease.path);
|
|
173
|
+
if (running) return { blocked: { reason: 'suite', holders: [running] } };
|
|
174
|
+
updateLease(lease, { node: 'tests' });
|
|
175
|
+
return { lease };
|
|
176
|
+
}, ctx);
|
|
177
|
+
return guarded ? guarded.value : { blocked: { reason: 'contended', holders: [] } };
|
|
178
|
+
};
|
|
179
|
+
return pollClaim(claim, { ...opts, now, sleep, log });
|
|
180
|
+
}
|
|
181
|
+
|
|
146
182
|
// For a holder whose next node needs more RAM than it was admitted with: it keeps its slot and
|
|
147
183
|
// waits only on memory, since re-claiming would hit the re-entrant path and skip the check.
|
|
148
184
|
export function awaitFreeMemory(opts) {
|
|
@@ -206,6 +242,7 @@ export function describeBusy(result, config) {
|
|
|
206
242
|
const key = result.kind ? `heavy.minFreeMemGbByKind.${result.kind}` : 'heavy.minFreeMemGb';
|
|
207
243
|
return `sem RAM livre há ${waited}s (${result.freeGb?.toFixed(1)} GB < ${floor} GB mínimos${kindSuffix(result.kind)}, ${key})`;
|
|
208
244
|
}
|
|
245
|
+
if (result.reason === 'suite') return `o verify de outra feature segue na suíte de testes após ${waited}s (heavy.waitTimeoutSec)`;
|
|
209
246
|
if (result.reason === 'exclusive') return `limpeza do servidor de build ainda em curso após ${waited}s`;
|
|
210
247
|
return `${result.holders.length}/${config.slots} slots pesados ocupados por ${waited}s (heavy.waitTimeoutSec)`;
|
|
211
248
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"expectedCount": {
|
|
58
58
|
"type": "integer",
|
|
59
59
|
"minimum": 0,
|
|
60
|
-
"description": "Recorded baseline. Written ONLY by `morph-spec verify --record-baseline`, from a green run — never typed by hand."
|
|
60
|
+
"description": "Recorded baseline, kept only for `node-test` and `evals` (ignored, and removed by --record-baseline, on the other runners). Written ONLY by `morph-spec verify --record-baseline`, from a green run — never typed by hand."
|
|
61
61
|
},
|
|
62
62
|
"timeoutMs": {
|
|
63
63
|
"type": "integer",
|
|
@@ -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.8.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.8.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.8.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-apply
|
|
@@ -129,7 +129,7 @@ config **antes** do verify: o stamp grava o HEAD, e código ou `config.json` com
|
|
|
129
129
|
|
|
130
130
|
Depois `morph-spec verify {feature} --json`, **sem filtro** — nunca recorte o
|
|
131
131
|
verify da feature. `--filter` é do loop do próprio sub-agente; o Gate 3 e o CI rodam a suíte
|
|
132
|
-
inteira. Nó
|
|
132
|
+
inteira. Nó `node-test`/`evals` sem `expectedCount`, ou fatia que apagou teste de propósito (limpeza, `morph-spec tests-audit`): acrescente `--record-baseline` a ESTA rodada — ela mede e
|
|
133
133
|
prova de uma vez; nunca um segundo verify só para gravar a baseline. Vermelho: corrija com `verify.feedback` (direto no código, ou re-despachando a fatia dona)
|
|
134
134
|
e rode de novo; **3 tentativas** sem verde → `AskUserQuestion` ("Verify de {feature} vermelho após 3
|
|
135
135
|
tentativas — aceitar com ressalva, continuar tentando, ou parar?"). Quem conta é o `feature.json`:
|
|
@@ -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.8.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.8.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.8.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.8.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -106,7 +106,7 @@ O `create` já escreveu o esqueleto em `2-plan/plan.md` (leia e preencha; `chore
|
|
|
106
106
|
|
|
107
107
|
**Contrato de implementação**: o que quem implementa precisa e o código não diz.
|
|
108
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
|
-
- **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.
|
|
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` (só `node-test`/`evals`; nos outros runners vale o piso zero) 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
|
|
|
112
112
|
Plano bom cabe em duas páginas; crescendo além, corte escopo. Feche com `morph-spec plan-check {feature}` até zero erro. Ele também recusa veredito de reuso fora do enum, id `C`/`F`/`ADR` duplicado, `## Errata` fora do fim, `## Cenários` vazio e fatia sem `persona:`; avisa chave de fatia desconhecida, superfície fora da `ui-designer`, ADR sem **Alcance**, fatia sem `cobre:` e mais de 12 fatias.
|
|
@@ -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.8.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -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.8.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.8.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-uiux
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<templateSet group="MORPH Templates">
|
|
2
|
+
<template name="morph-pascal" value="{{pascalCase $FEATURE_NAME$}}" description="Convert to PascalCase (UserAuth)" toReformat="false" toShortenFQNames="true">
|
|
3
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
4
|
+
<context>
|
|
5
|
+
<option name="OTHER" value="true" />
|
|
6
|
+
</context>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<template name="morph-camel" value="{{camelCase $FEATURE_NAME$}}" description="Convert to camelCase (userAuth)" toReformat="false" toShortenFQNames="true">
|
|
10
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
11
|
+
<context>
|
|
12
|
+
<option name="OTHER" value="true" />
|
|
13
|
+
</context>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<template name="morph-snake" value="{{snakeCase $FEATURE_NAME$}}" description="Convert to snake_case (user_auth)" toReformat="false" toShortenFQNames="true">
|
|
17
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
18
|
+
<context>
|
|
19
|
+
<option name="OTHER" value="true" />
|
|
20
|
+
</context>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<template name="morph-title" value="{{titleCase $FEATURE_NAME$}}" description="Convert to Title Case (User Auth)" toReformat="false" toShortenFQNames="true">
|
|
24
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
25
|
+
<context>
|
|
26
|
+
<option name="OTHER" value="true" />
|
|
27
|
+
</context>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<template name="morph-kebab" value="{{kebabCase $FEATURE_NAME$}}" description="Convert to kebab-case (user-auth)" toReformat="false" toShortenFQNames="true">
|
|
31
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
32
|
+
<context>
|
|
33
|
+
<option name="OTHER" value="true" />
|
|
34
|
+
</context>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<template name="morph-if" value="{{#if $CONDITION$}} $END$ {{/if}}" description="Handlebars if block" toReformat="false" toShortenFQNames="true">
|
|
38
|
+
<variable name="CONDITION" expression="" defaultValue=""condition"" alwaysStopAt="true" />
|
|
39
|
+
<context>
|
|
40
|
+
<option name="OTHER" value="true" />
|
|
41
|
+
</context>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<template name="morph-if-else" value="{{#if $CONDITION$}} $TRUE_BRANCH$ {{else}} $FALSE_BRANCH$ {{/if}}" description="Handlebars if-else block" toReformat="false" toShortenFQNames="true">
|
|
45
|
+
<variable name="CONDITION" expression="" defaultValue=""condition"" alwaysStopAt="true" />
|
|
46
|
+
<variable name="TRUE_BRANCH" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
47
|
+
<variable name="FALSE_BRANCH" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
48
|
+
<context>
|
|
49
|
+
<option name="OTHER" value="true" />
|
|
50
|
+
</context>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<template name="morph-each" value="{{#each $ITEMS$}} {{this.$PROPERTY$}} {{/each}}" description="Handlebars each loop" toReformat="false" toShortenFQNames="true">
|
|
54
|
+
<variable name="ITEMS" expression="" defaultValue=""items"" alwaysStopAt="true" />
|
|
55
|
+
<variable name="PROPERTY" expression="" defaultValue=""name"" alwaysStopAt="true" />
|
|
56
|
+
<context>
|
|
57
|
+
<option name="OTHER" value="true" />
|
|
58
|
+
</context>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<template name="morph-service" value="namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}}.Services; public class {{pascalCase FEATURE_NAME}}Service( I{{pascalCase FEATURE_NAME}}Repository repository, ILogger<{{pascalCase FEATURE_NAME}}Service> logger) : I{{pascalCase FEATURE_NAME}}Service { public async Task<{{pascalCase FEATURE_NAME}}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default) { $END$ } }" description="MORPH .NET Service class template" toReformat="false" toShortenFQNames="true">
|
|
62
|
+
<context>
|
|
63
|
+
<option name="C_SHARP" value="true" />
|
|
64
|
+
</context>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<template name="morph-repository" value="public class {{pascalCase FEATURE_NAME}}Repository(AppDbContext context) : I{{pascalCase FEATURE_NAME}}Repository { private readonly DbSet<{{pascalCase FEATURE_NAME}}> _dbSet = context.Set<{{pascalCase FEATURE_NAME}}>(); public async Task<{{pascalCase FEATURE_NAME}}?> GetByIdAsync(int id, CancellationToken cancellationToken = default) { $END$ } }" description="MORPH .NET Repository class template" toReformat="false" toShortenFQNames="true">
|
|
68
|
+
<context>
|
|
69
|
+
<option name="C_SHARP" value="true" />
|
|
70
|
+
</context>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<template name="morph-component" value="@page "/{{kebabCase FEATURE_NAME}}" @inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service @inject ILogger<{{pascalCase FEATURE_NAME}}Page> Logger <PageTitle>{{pascalCase FEATURE_NAME}}</PageTitle> <div class="container-fluid"> $END$ </div> @code { }" description="MORPH Blazor component template" toReformat="false" toShortenFQNames="true">
|
|
74
|
+
<context>
|
|
75
|
+
<option name="HTML" value="true" />
|
|
76
|
+
</context>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<template name="morph-migration" value="public partial class Add{{pascalCase FEATURE_NAME}} : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "{{pascalCase FEATURE_NAME}}s", columns: table => new { Id = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), $END$ }, constraints: table => { table.PrimaryKey("PK_{{pascalCase FEATURE_NAME}}s", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable(name: "{{pascalCase FEATURE_NAME}}s"); } }" description="MORPH EF Core migration template" toReformat="false" toShortenFQNames="true">
|
|
80
|
+
<context>
|
|
81
|
+
<option name="C_SHARP" value="true" />
|
|
82
|
+
</context>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<template name="morph-ts-contract" value="export interface {{pascalCase FEATURE_NAME}} { id: number; $FIELD1$ } export interface Create{{pascalCase FEATURE_NAME}}Request { $FIELD2$ } export interface Update{{pascalCase FEATURE_NAME}}Request { $END$ }" description="MORPH TypeScript contract template" toReformat="false" toShortenFQNames="true">
|
|
86
|
+
<variable name="FIELD1" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
87
|
+
<variable name="FIELD2" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
88
|
+
<context>
|
|
89
|
+
<option name="TypeScript" value="true" />
|
|
90
|
+
</context>
|
|
91
|
+
</template>
|
|
92
|
+
</templateSet>
|