@polymorphism-tech/morph-spec 9.8.0 → 10.1.1

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 (202) hide show
  1. package/CLAUDE.md +12 -10
  2. package/README.md +4 -4
  3. package/bin/morph-spec.js +40 -43
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +28 -36
  6. package/docs/QUICKSTART.md +31 -191
  7. package/framework/CLAUDE.md +24 -30
  8. package/framework/MORPH.md +47 -123
  9. package/framework/agents.json +6 -374
  10. package/framework/commands/morph-apply.md +1 -1
  11. package/framework/commands/morph-archive.md +4 -3
  12. package/framework/commands/morph-hotfix.md +8 -30
  13. package/framework/commands/morph-infra.md +2 -2
  14. package/framework/commands/morph-media.md +10 -9
  15. package/framework/commands/morph-preflight.md +3 -3
  16. package/framework/commands/morph-review.md +15 -0
  17. package/framework/commands/morph-status.md +1 -1
  18. package/framework/docs/CLI.md +84 -95
  19. package/framework/evals/review-checklist.md +45 -53
  20. package/framework/hooks/README.md +4 -4
  21. package/framework/hooks/claude-code/core/README.md +1 -1
  22. package/framework/hooks/claude-code/core/denylist.json +4 -4
  23. package/framework/hooks/claude-code/core/gate-guard.js +9 -0
  24. package/framework/hooks/claude-code/core/masked-exit.js +49 -0
  25. package/framework/hooks/claude-code/core/state-sync.js +4 -1
  26. package/framework/hooks/claude-code/core/telemetry-log.js +1 -2
  27. package/framework/hooks/claude-code/core/test-budget.js +10 -70
  28. package/framework/hooks/claude-code/core/validate-completion.js +11 -24
  29. package/framework/hooks/claude-code/core/worktree-context.js +5 -22
  30. package/framework/hooks/claude-code/core/worktree-create.js +6 -45
  31. package/framework/hooks/claude-code/core/worktree-env-inject.js +2 -10
  32. package/framework/hooks/shared/e2e-check.js +0 -0
  33. package/framework/hooks/shared/gate-event.js +2 -2
  34. package/framework/hooks/shared/graph-refresh.js +20 -3
  35. package/framework/hooks/shared/heavy-command.js +32 -1
  36. package/framework/hooks/shared/heavy-config.js +1 -1
  37. package/framework/hooks/shared/next-step.js +63 -24
  38. package/framework/hooks/shared/phase-utils.js +6 -9
  39. package/framework/hooks/shared/report-progress.js +86 -0
  40. package/framework/hooks/shared/state-reader.js +3 -2
  41. package/framework/hooks/statusline.js +332 -0
  42. package/framework/mcp/registry.json +4 -4
  43. package/framework/phases.json +32 -10
  44. package/framework/rules/ai-agents-standards.md +10 -2
  45. package/framework/rules/csharp-standards.md +4 -0
  46. package/framework/rules/data-standards.md +1 -1
  47. package/framework/rules/ef-standards.md +20 -0
  48. package/framework/rules/frontend-standards.md +1 -1
  49. package/framework/rules/hangfire-standards.md +1 -1
  50. package/framework/rules/implement-report.md +25 -0
  51. package/framework/rules/infrastructure-standards.md +2 -0
  52. package/framework/rules/neon-auth-standards.md +1 -1
  53. package/framework/rules/neon-standards.md +3 -3
  54. package/framework/rules/nextjs-standards.md +7 -0
  55. package/framework/rules/plan-grammar.md +53 -0
  56. package/framework/rules/ui-design.md +27 -0
  57. package/framework/schemas/config.schema.json +11 -18
  58. package/framework/schemas/feature.schema.json +7 -1
  59. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  60. package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +1 -1
  61. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +2 -2
  62. package/framework/skills/core/morph-apply/SKILL.md +39 -166
  63. package/framework/skills/core/morph-apply/references/debugging.md +8 -0
  64. package/framework/skills/core/morph-apply/references/hotfix.md +42 -0
  65. package/framework/skills/core/morph-apply/references/machine.md +29 -0
  66. package/framework/skills/core/{morph-media/SKILL.md → morph-apply/references/media.md} +11 -74
  67. package/framework/skills/core/morph-apply/references/review.md +65 -0
  68. package/framework/skills/core/morph-plan/SKILL.md +49 -109
  69. package/framework/skills/core/morph-plan/references/critique.md +43 -0
  70. package/framework/skills/core/morph-plan/references/interview.md +37 -0
  71. package/framework/skills/core/morph-uiux/SKILL.md +47 -64
  72. package/framework/skills/core/morph-uiux/surfaces.json +32 -0
  73. package/framework/standards/STANDARDS.json +51 -28
  74. package/framework/standards/STANDARDS.meta.json +16 -19
  75. package/framework/standards/ai-agents/agent-session.md +2 -3
  76. package/framework/standards/ai-agents/context-providers.md +2 -2
  77. package/framework/standards/ai-agents/durable-workflows-hitl.md +2 -2
  78. package/framework/standards/ai-agents/providers/model-registry.md +2 -2
  79. package/framework/standards/ai-agents/rag-custom-pgvector.md +7 -6
  80. package/framework/standards/ai-agents/setup.md +1 -1
  81. package/framework/standards/ai-agents/tool-adapters.md +2 -2
  82. package/framework/standards/architecture/stack-baseline.md +70 -0
  83. package/framework/standards/backend/database/postgresql/database.md +22 -1
  84. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +33 -1
  85. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +2 -1
  86. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +12 -13
  87. package/framework/standards/frontend/design-system/ai-image-generation.md +51 -12
  88. package/framework/standards/frontend/design-system/ai-video-generation.md +50 -4
  89. package/framework/standards/frontend/design-system/art-direction.md +2 -2
  90. package/framework/standards/frontend/scroll-driven/frame-scrub.md +12 -2
  91. package/framework/standards/frontend/scroll-driven/scroll-components.md +76 -4
  92. package/framework/standards/infrastructure/docker/local-compose-isolation.md +2 -2
  93. package/framework/standards/infrastructure/neon/neon-pgvector.md +62 -20
  94. package/framework/standards/infrastructure/neon/neon-platform.md +61 -0
  95. package/framework/standards/infrastructure/neon/neon-setup.md +14 -13
  96. package/framework/standards/integration/mcp/mcp-tools.md +20 -20
  97. package/framework/templates/REGISTRY.json +1 -2
  98. package/framework/templates/context/ARCHITECTURE.md +1 -1
  99. package/framework/templates/docs/plan.md +15 -12
  100. package/framework/templates/docs/ui-mockups.md +17 -105
  101. package/framework/templates/infrastructure/docker/docker-compose.template.yml +1 -1
  102. package/package.json +1 -1
  103. package/src/commands/e2e/e2e.js +88 -23
  104. package/src/commands/graph/diff.js +74 -0
  105. package/src/commands/graph/status.js +1 -1
  106. package/src/commands/observability/cost.js +2 -4
  107. package/src/commands/observability/telemetry.js +1 -1
  108. package/src/commands/project/doctor.js +23 -39
  109. package/src/commands/project/fleet.js +5 -6
  110. package/src/commands/project/init.js +63 -36
  111. package/src/commands/project/stack-scan.js +3 -42
  112. package/src/commands/project/stack-set.js +26 -0
  113. package/src/commands/project/status.js +12 -13
  114. package/src/commands/project/update.js +39 -3
  115. package/src/commands/project/worktree.js +3 -14
  116. package/src/commands/state/advance.js +17 -17
  117. package/src/commands/state/approve.js +0 -6
  118. package/src/commands/state/archive.js +27 -1
  119. package/src/commands/state/auto-approval.js +2 -2
  120. package/src/commands/state/create.js +1 -6
  121. package/src/commands/state/gate-check.js +38 -31
  122. package/src/commands/state/gate-decision.js +3 -2
  123. package/src/commands/state/next.js +1 -0
  124. package/src/commands/state/recap.js +67 -0
  125. package/src/commands/state/report-check.js +36 -178
  126. package/src/commands/validation/plan-check.js +16 -11
  127. package/src/commands/validation/validate-feature.js +3 -3
  128. package/src/commands/validation/verify.js +3 -6
  129. package/src/core/paths/output-schema.js +3 -3
  130. package/src/core/state/state-manager.js +2 -4
  131. package/src/dashboard/app/components/CommandPalette.jsx +2 -5
  132. package/src/dashboard/app/components/GatePauses.jsx +116 -0
  133. package/src/dashboard/app/components/SessionMap.jsx +77 -0
  134. package/src/dashboard/app/pages/Activity.jsx +4 -0
  135. package/src/dashboard/app/pages/AgentMap.jsx +1 -2
  136. package/src/dashboard/app/pages/FeatureDetail.jsx +129 -186
  137. package/src/dashboard/app/pages/Features.jsx +7 -7
  138. package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
  139. package/src/dashboard/app/pages/Portfolio.jsx +40 -5
  140. package/src/dashboard/scanner/activity-scanner.js +29 -0
  141. package/src/dashboard/scanner/agents-scanner.js +11 -14
  142. package/src/dashboard/scanner/features-scanner.js +45 -24
  143. package/src/dashboard/scanner/project-api.js +11 -2
  144. package/src/dashboard/scanner/skills-scanner.js +11 -1
  145. package/src/lib/detectors/index.js +1 -1
  146. package/src/lib/e2e/applicability.js +3 -1
  147. package/src/lib/git/tree-state.js +1 -16
  148. package/src/lib/graph/diff-tree.js +127 -0
  149. package/src/lib/graph/graphify-runtime.js +12 -5
  150. package/src/lib/installers/memory-installer.js +3 -3
  151. package/src/lib/installers/rules-installer.js +57 -0
  152. package/src/lib/installers/stack-assets.js +17 -0
  153. package/src/lib/installers/stack-sync.js +219 -0
  154. package/src/lib/plan/checks.js +61 -109
  155. package/src/lib/plan/critique.js +28 -0
  156. package/src/lib/plan/media.js +42 -0
  157. package/src/lib/plan/plan-html.js +125 -0
  158. package/src/lib/plan/plan-parser.js +34 -45
  159. package/src/lib/recap/piper.js +36 -0
  160. package/src/lib/recap/recap.js +232 -0
  161. package/src/lib/review-reports.js +28 -4
  162. package/src/lib/risk-detector.js +90 -11
  163. package/src/lib/specs/screen-checklist.js +33 -48
  164. package/src/lib/stack/dotnet-build-ignores.js +8 -9
  165. package/src/lib/telemetry/metrics.js +27 -29
  166. package/src/lib/validators/reuse/reuse-validator.js +1 -9
  167. package/src/lib/validators/verify-runner.js +0 -7
  168. package/src/lib/worktree/fleet-scanner.js +7 -20
  169. package/src/lib/worktree/worktree-audit.js +0 -37
  170. package/src/scripts/setup-infra.js +15 -50
  171. package/src/utils/agents-installer.js +27 -201
  172. package/src/utils/hooks-installer.js +11 -95
  173. package/framework/hooks/claude-code/core/warn-bash-source-write.js +0 -23
  174. package/framework/hooks/claude-code/statusline.py +0 -699
  175. package/framework/hooks/claude-code/statusline.sh +0 -11
  176. package/framework/hooks/shared/agent-reap.js +0 -74
  177. package/framework/hooks/shared/slice-progress.js +0 -110
  178. package/framework/skills/core/morph-hotfix/SKILL.md +0 -133
  179. package/framework/skills/core/morph-machine/SKILL.md +0 -46
  180. package/framework/skills/core/morph-review/SKILL.md +0 -129
  181. package/framework/skills/core/morph-standards/SKILL.md +0 -52
  182. package/framework/standards/PROVENANCE.json +0 -119
  183. package/framework/standards/infrastructure/neon/neon-pgrag.md +0 -243
  184. package/src/commands/project/worktree-reap.js +0 -34
  185. package/src/commands/state/handoff.js +0 -65
  186. package/src/commands/validation/mutate.js +0 -263
  187. package/src/commands/validation/scout-check.js +0 -28
  188. package/src/lib/generators/handoff-generator.js +0 -172
  189. package/src/lib/mutation/adapters.js +0 -79
  190. package/src/lib/mutation/commands.js +0 -61
  191. package/src/lib/mutation/dotnet-introspection.js +0 -281
  192. package/src/lib/mutation/manifest.js +0 -144
  193. package/src/lib/mutation/plan.js +0 -44
  194. package/src/lib/mutation/report.js +0 -68
  195. package/src/lib/mutation/runner.js +0 -301
  196. package/src/lib/mutation/schemata.js +0 -60
  197. package/src/lib/mutation/snapshot.js +0 -65
  198. package/src/lib/mutation/subset.js +0 -109
  199. package/src/lib/mutation/trx.js +0 -50
  200. package/src/lib/scout/scout-check.js +0 -293
  201. package/src/lib/validators/traceability-check.js +0 -103
  202. package/src/lib/worktree/agent-reaper.js +0 -247
@@ -216,10 +216,10 @@ The agent decides autonomously when to call `VectorSearchTool.SearchAsync`. It r
216
216
 
217
217
  ## Ingestão delta por lookup-key
218
218
 
219
- **O defeito que esta seção existe para matar é o apaga-e-regrava.** `KbStore.IngestAsync` do GHLBrain faz `DELETE … WHERE location_id AND source` e re-insere/re-embeda **tudo** do `source` a cada ingestão, mudou ou não (`docs/specs/elevacao-9/brutos/scan-ghlbrain.md:412`): 288 fichas re-embedadas, uma chamada de embedding por item, a cada reindex em massa do catálogo — e, com id posicional, inserir um trecho no topo de um documento torna "novos" todos os de baixo.
219
+ **O defeito que esta seção existe para matar é o apaga-e-regrava.** O `KbStore.IngestAsync` de um projeto em produção faz `DELETE … WHERE location_id AND source` e re-insere/re-embeda **tudo** do `source` a cada ingestão, mudou ou não: 288 fichas re-embedadas, uma chamada de embedding por item, a cada reindex em massa do catálogo — e, com id posicional, inserir um trecho no topo de um documento torna "novos" todos os de baixo.
220
220
 
221
- > [!warning] Errata (2026-09-15, medida no código e no Neon de produção — Morph.GHLBrain#96)
222
- > A versão anterior deste parágrafo afirmava também uma **janela** entre o apagar e o gravar em que a busca não acha nada. **No GHLBrain ela não existe:** `ConvVectorSql.ExecuteAsync` roda o `DELETE` e os `INSERT`s numa transação só, e o embedding acontece **antes** de a transação abrir — em READ COMMITTED o leitor vê o conteúdo antigo até o commit. O custo medido também é pequeno (324 trechos, ~US$ 0,0005 por re-embed completo, 1 trecho por `source`): lá, o ganho real do delta é **latência** (o reindex em massa) e **prevenção** (documento de até ~70 trechos). A janela é real em quem apaga fora da transação que grava — é o caso que a regra 4 abaixo cobre.
221
+ > [!warning] Errata (2026-09-15, medida no código e no Neon de produção)
222
+ > A versão anterior deste parágrafo afirmava também uma **janela** entre o apagar e o gravar em que a busca não acha nada. **Nesse projeto ela não existe:** `ConvVectorSql.ExecuteAsync` roda o `DELETE` e os `INSERT`s numa transação só, e o embedding acontece **antes** de a transação abrir — em READ COMMITTED o leitor vê o conteúdo antigo até o commit. O custo medido também é pequeno (324 trechos, ~US$ 0,0005 por re-embed completo, 1 trecho por `source`): lá, o ganho real do delta é **latência** (o reindex em massa) e **prevenção** (documento de até ~70 trechos). A janela é real em quem apaga fora da transação que grava — é o caso que a regra 4 abaixo cobre.
223
223
 
224
224
  ### O contrato — uma função pura, sem I/O
225
225
 
@@ -251,7 +251,7 @@ public sealed record DeltaPlan<T>(
251
251
  1. **`Unchanged` não é re-embedado.** É a economia inteira. Num republish típico a lista grande é `Unchanged` e as outras três são curtas — e é exatamente isso que a ingestão anterior escondia.
252
252
  2. **`Removed` são as chaves presentes no acervo e ausentes do `incoming`.** Sem esse balde, conteúdo apagado na origem continua sendo devolvido pela busca.
253
253
  3. **O id continua determinístico** — `DeterministicUuid.V5(namespace, "{loc}:{source}:{idx}")` já é a prática — para que `Updated` seja **upsert**, não delete+insert. Delete+insert quebra referências e troca o id de um chunk que só mudou de texto.
254
- **Ressalva medida (Morph.GHLBrain#96, ADR-2 da `delta-ingestion`):** quando o item é um *trecho* de documento, a posição como lookup-key derrota a regra 1 — inserir um trecho no topo desloca os índices e marca todo trecho abaixo como `Updated`, re-embedando tudo. Ali a chave certa é o **hash do conteúdo** (`lookupKey` e `contentHash` recebem o mesmo delegado): `Updated` fica vazio por construção, editar um trecho vira `Added` + `Removed`, e trechos idênticos são deduplicados antes do `Plan` para a chave duplicada nunca disparar. A chave posicional continua certa quando o item tem identidade própria (uma ficha por produto, um documento inteiro).
254
+ **Ressalva medida em produção:** quando o item é um *trecho* de documento, a posição como lookup-key derrota a regra 1 — inserir um trecho no topo desloca os índices e marca todo trecho abaixo como `Updated`, re-embedando tudo. Ali a chave certa é o **hash do conteúdo** (`lookupKey` e `contentHash` recebem o mesmo delegado): `Updated` fica vazio por construção, editar um trecho vira `Added` + `Removed`, e trechos idênticos são deduplicados antes do `Plan` para a chave duplicada nunca disparar. A chave posicional continua certa quando o item tem identidade própria (uma ficha por produto, um documento inteiro).
255
255
  4. **Sem janela vazia: nunca apague antes de gravar fora da transação que grava.** Apagar e gravar em transações separadas (ou sem transação) é o que faz a busca devolver vazio durante a reingestão. Aplique `Added`/`Updated` primeiro e só então `Removed`, dentro da mesma transação — com a transação a janela já não existe, e a ordem mantém o código correto se um dia ela for partida.
256
256
 
257
257
  ### Chave duplicada no `incoming` é erro de entrada
@@ -273,7 +273,7 @@ Os dois não competem; resolvem problemas diferentes. Adote o writer quando acei
273
273
 
274
274
  ### Item aberto, medido em campo
275
275
 
276
- **Threshold de similaridade e `k` por escopo ainda não têm regra.** Hoje o escopo institucional da base do GHLBrain roda **sem `LIMIT`** (`scan-ghlbrain.md:410`): a busca devolve tudo o que passa do threshold, e o custo do prompt varia com o tamanho da base. Definir `k` e o corte por escopo é trabalho pendente, não recomendação existente — registrado aqui para não ser reinventado como se fosse novo.
276
+ **Threshold de similaridade e `k` por escopo ainda não têm regra.** Hoje o escopo institucional da base desse projeto roda **sem `LIMIT`**: a busca devolve tudo o que passa do threshold, e o custo do prompt varia com o tamanho da base. Definir `k` e o corte por escopo é trabalho pendente, não recomendação existente — registrado aqui para não ser reinventado como se fosse novo.
277
277
 
278
278
  ---
279
279
 
@@ -301,6 +301,7 @@ ALTER TABLE kb_embeddings ENABLE ROW LEVEL SECURITY;
301
301
  | Anti-pattern | Why it's wrong | Right way |
302
302
  |--------------|----------------|-----------|
303
303
  | Hosted RAG (upload PDFs to a service) | Loses control of chunking, embedding, and storage; vendor lock-in | Custom RAG: own all three layers |
304
+ | Semantic search only, over a corpus with codes, SKUs or proper names | Embeddings blur exact terms; the right document never reaches the LLM | Hybrid search with RRF in the same Postgres (`infrastructure-neon-pgvector#hybrid-search-vector--full-text`), measured by eval |
304
305
  | Embeddings column without an index | Full table scan — 100 ms becomes 30 s at 100k rows | HNSW index on the embedding column always |
305
306
  | One fixed chunk size for all document types | Contracts need dense sentences; catalogs need short snippets; mixed chunks degrade recall | Choose strategy per document type (see `data-vector-search-rag-chunking`) |
306
307
  | `tenant_id` as a tool parameter the LLM passes | LLM can be prompted to pass any GUID — cross-tenant data leak | Inject `tenant_id` from auth context at construction; never expose as a model-facing parameter |
@@ -340,4 +341,4 @@ ALTER TABLE kb_embeddings ENABLE ROW LEVEL SECURITY;
340
341
 
341
342
  ---
342
343
 
343
- *MORPH-SPEC by Polymorphism Tech — ai-agents/rag-custom-pgvector.md v2.1 (2026-09-15 — errata da janela no GHLBrain; ressalva da chave por hash)*
344
+ *MORPH-SPEC by Polymorphism Tech — ai-agents/rag-custom-pgvector.md v2.1 (2026-09-15 — errata da janela; ressalva da chave por hash)*
@@ -35,7 +35,7 @@ Está adicionando um agente AI a um projeto .NET?
35
35
 
36
36
  ## 1. Packages (NuGet)
37
37
 
38
- **Nenhuma faixa flutuante.** Um SDK de cadência semanal com `2.*` é como o ProspectPRO comprou um `MissingMethodException` na Responses API **depois de a chamada ser paga**. Versões abaixo: as do `framework/ai-pin.json` (`pinnedAt` 2026-09-08) ou, quando o pacote **não está no pin**, a versão apurada no nuget.org em **2026-09-08**, dita como tal.
38
+ **Nenhuma faixa flutuante.** Um SDK de cadência semanal com `2.*` é como um projeto em produção comprou um `MissingMethodException` na Responses API **depois de a chamada ser paga**. Versões abaixo: as do `framework/ai-pin.json` (`pinnedAt` 2026-09-08) ou, quando o pacote **não está no pin**, a versão apurada no nuget.org em **2026-09-08**, dita como tal.
39
39
 
40
40
  **Required for any MAF project:**
41
41
 
@@ -5,7 +5,7 @@
5
5
  > **Keywords:** tool, AIFunctionFactory, adaptador de tool, tool fina, Ops, Desfecho, Render, regra de negócio em tool, descrição da tool, golden de tool, refactor neutro, migração de teste
6
6
  > **Read by Claude in:** implement (ao escrever ou migrar uma tool de agente que carrega regra de negócio)
7
7
 
8
- **Verified against:** Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08); os dois blocos C# abaixo foram extraídos com `scripts/extract-doc-csharp.mjs` e compilados contra o pin com `TreatWarningsAsErrors`, e os três testes do segundo bloco foram executados e passam. **A separação em si e a migração de teste são verificação documental:** vêm da feature `tools-finas` do MORPH_GHLBrain e o ai-kit não as traz. Last-verified: 2026-09-08.
8
+ **Verified against:** Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08); os dois blocos C# abaixo foram extraídos com `scripts/extract-doc-csharp.mjs` e compilados contra o pin com `TreatWarningsAsErrors`, e os três testes do segundo bloco foram executados e passam. **A separação em si e a migração de teste são verificação documental:** vêm de um projeto .NET em produção e o ai-kit não as traz. Last-verified: 2026-09-08.
9
9
 
10
10
  ---
11
11
 
@@ -201,7 +201,7 @@ A migração é um refactor que **não pode mudar o que o modelo lê**. Por isso
201
201
 
202
202
  ## Exemplos reais
203
203
 
204
- MORPH_GHLBrain, `src/Morph.GHLBrain/Features/Conversation/Operator/`:
204
+ Um agente conversacional .NET em produção, na pasta da feature do operador:
205
205
 
206
206
  | Tool | Adaptador | `Ops` | Desfecho |
207
207
  |---|---|---|---|
@@ -0,0 +1,70 @@
1
+ # Stack do Morph — a recomendação para projeto novo
2
+
3
+ > **Scope:** universal
4
+ > **Layer:** 0 (lido pela `morph-plan` quando o projeto ainda não declara `project.stack`, e pelo `init` num diretório vazio)
5
+ > **Keywords:** stack, greenfield, projeto novo, .NET, VSA, EF Core, Neon, Next.js, Shadcn, MAF, Hangfire, postgres first, fila, grafo, jsonb, serviço de dados
6
+ > **Load When:** escolhendo a stack de um projeto novo, julgando um desvio de stack no plano, ou diante de um serviço de dados novo (cache, fila, vetor, busca, grafo, banco de documento)
7
+
8
+ **Verified against:** .NET 10, EF Core 10, Next.js 16 App Router, Microsoft Agent Framework 1.0; catálogo de extensões do Neon lido em 2026-10-30. Last-verified: 2026-10-30.
9
+
10
+ ---
11
+
12
+ ## A stack
13
+
14
+ Projeto novo começa nela **inteira** (`dotnet+nextjs --with neon`), mesmo quando a primeira feature caberia em menos: o projeto cresce, e a stack decidida no primeiro dia é a que carrega as regras certas. Recorte ou outra tecnologia é escolha explícita do usuário, nunca a recomendação. É a que os standards, as rules de domínio e o `stack-scan` conhecem.
15
+
16
+ | Camada | Stack | Tag do `project.stack` |
17
+ | --- | --- | --- |
18
+ | Backend | .NET 10 + Vertical Slice (pasta por feature: handler + validator + endpoint + errors + tests) | `dotnet` |
19
+ | ORM/DB | EF Core + Neon (Postgres) | `neon` em `project.integrations` |
20
+ | Erros | Result pattern com `result.Match()` | — |
21
+ | IA | Microsoft Agent Framework (MAF), quando houver agentes | `maf` em `project.integrations` |
22
+ | Frontend | Next.js App Router + Tailwind + Motion + Shadcn | `nextjs` |
23
+ | Jobs async | Hangfire, só com razão real | — |
24
+ | Local | Docker Compose para as dependências, portas por variável | `docker` em `project.integrations` |
25
+
26
+ Recortes, quando o usuário os escolhe: só API (`dotnet`), só site ou app web (`nextjs`).
27
+
28
+ ## Postgres primeiro
29
+
30
+ Necessidade nova de dado começa no Postgres que o projeto já tem. Serviço a menos é menos rede, menos operação e um lugar só para depurar. Sair dele é decisão medida, registrada como ADR que diz por que o Postgres não cobre: nunca antecipação de um problema que ainda não existe.
31
+
32
+ | Necessidade | No Postgres | Sai quando |
33
+ | --- | --- | --- |
34
+ | Dado flexível, documento | Coluna tipada; `jsonb` só no que varia (`backend-database-postgresql-database#jsonb`) | Quase nunca: banco de documento pede ADR (`data-nosql-cosmos-db`) |
35
+ | RAG, busca semântica | pgvector + HNSW (`ai-agents-rag-custom-pgvector`) | O corpus estoura o HNSW: `lakebase_vector`, com ADR |
36
+ | Busca por termo, híbrida | `tsvector` + GIN + RRF (`infrastructure-neon-pgvector#hybrid-search-vector--full-text`) | Eval mostra ranking ruim: `lakebase_text`, com ADR |
37
+ | Job, fila | Hangfire ou claim com `SKIP LOCKED` (`backend-dotnet-vsa-handler-patterns#queue-on-postgres`) | Vazão medida acima do que o banco aguenta, ou consumidores fora da app |
38
+ | Grafo, hierarquia | Chave estrangeira, CTE recursiva, `ltree` | Travessia profunda é o produto, e a medida mostra a CTE no limite |
39
+ | Cache | Nenhum: índice e consulta primeiro | Latência medida que o índice não resolve: `data-nosql-cache-redis` |
40
+
41
+ Arquivo, gateway de LLM, ambiente descartável e diagnóstico de consulta o próprio Neon entrega: confira `infrastructure-neon-platform` antes de contratar serviço.
42
+
43
+ Grafo e ontologia não são requisito de RAG: dado bem modelado, com metadados no chunk, recupera bem. Testar é barato; adotar sem eval, não.
44
+
45
+ **Fora do catálogo do Neon** (verificado em 2026-10-30): `pg_search` foi removido e `pgrag` está deprecado; `pgmq` e Apache AGE não estão na lista de extensões, então `CREATE EXTENSION` falha. O `pgmq` tem instalação só-SQL, não testada aqui, e o claim com `SKIP LOCKED` já o dispensa. Tutorial que manda subir um deles vale para Postgres próprio ou Supabase; usar na nossa stack exige ADR de hospedagem.
46
+
47
+ **Custo que o Neon cobra:** worker que faz polling mantém o compute acordado e anula o scale to zero. Fila no banco é barata em serviço, não em compute.
48
+
49
+ **Escala:** a OpenAI serve 800 milhões de usuários com um primário e quase 50 réplicas de leitura, mas mandou write-heavy novo para sistemas shardados (Cosmos DB); a RudderStack chegou a 100 mil eventos/s com fila em Postgres particionada em tabelas descartadas por `DROP`. Postgres não é para sempre: é o ponto de partida que aguenta até uma medida dizer o contrário.
50
+
51
+ ## Declarar
52
+
53
+ `morph-spec stack-set dotnet+nextjs --with neon` (mais `maf` quando houver agentes) grava a stack no `config.json` e
54
+ instala tudo o que ela carrega: rules de domínio em `.claude/rules/`, commands e skills filtrados pela stack. As rules só carregam no início de uma sessão do Claude Code: a sessão que implementa
55
+ começa depois do `stack-set`.
56
+
57
+ ## Padrões proibidos
58
+
59
+ Os standards detalham; o build, o lint e o `stack-scan` cobram o mecânico.
60
+
61
+ - Exception como fluxo de controle: o Result põe o erro esperado no tipo.
62
+ - Service/Manager entre o handler e os dados.
63
+ - `IRepository<T>`/`IUnitOfWork`: o `DbSet<T>` já é o repository.
64
+ - SQL cru sem ADR.
65
+ - Redeclarar tipo que já existe: antes, `morph-spec graph explain <nome>` (sem grafo, `grep -rn`); criar exige `CRIAR` com `why` em `## Reuso`.
66
+
67
+ ## Fora da stack
68
+
69
+ Outra tecnologia é decisão do usuário, registrada como ADR no plano (`**Alcance:** projeto`). O harness segue
70
+ funcionando, mas sem rule de domínio para ela: as regras viram critério escrito no plano.
@@ -5,7 +5,7 @@
5
5
  > **Keywords:** postgresql, postgres, sql, neon, database schema
6
6
  > **Load When:** neon or postgresql keywords detected
7
7
 
8
- **Verified against:** PostgreSQL 16/17 + EF Core 10.0.12 (Npgsql provider 10.0.3) + @neondatabase/serverless. The EF Core query blocks compile with `TreatWarningsAsErrors` inside a wrapper method over stand-in entities; the SQL is documentary. Last-verified: 2026-10-03.
8
+ **Verified against:** PostgreSQL 16/17 + EF Core 10.0.12 (Npgsql provider 10.0.3) + @neondatabase/serverless. The EF Core query blocks compile with `TreatWarningsAsErrors` inside a wrapper method over stand-in entities; the SQL is documentary. Last-verified: 2026-10-30.
9
9
 
10
10
  ---
11
11
 
@@ -221,6 +221,27 @@ create index idx_posts_content_search
221
221
 
222
222
  ---
223
223
 
224
+ ## JSONB
225
+
226
+ Model first. A column with a type and a constraint is what lets you trust the data a year from now; a
227
+ bag of JSON with no owner ends in a migration that takes months and loses what nobody can interpret.
228
+
229
+ - **`jsonb` holds only what truly varies per row**: document metadata, a provider payload kept for
230
+ audit, a rendered spec. Never the whole entity "because we don't know the shape yet".
231
+ - **What you filter, join, sort or aggregate is a column.** A field that started in `jsonb` and became
232
+ a query gets promoted, in a migration.
233
+ - **The app validates the shape on write.** `jsonb` checks that it is JSON, nothing else.
234
+ - **Index the query you have:** `USING gin (metadata jsonb_path_ops)` for containment (`@>`), or a
235
+ btree expression index on one key.
236
+
237
+ ```sql
238
+ create index idx_documents_metadata on public.documents using gin (metadata jsonb_path_ops);
239
+
240
+ select id from public.documents where metadata @> '{"source": "manual"}';
241
+ ```
242
+
243
+ ---
244
+
224
245
  ## Functions & Triggers
225
246
 
226
247
  ### Auto-update Timestamp
@@ -5,7 +5,7 @@
5
5
  > **Keywords:** vsa, handler, feature-slice, vertical-slice, minimal-api, endpoint
6
6
  > **Load When:** dotnet VSA projects
7
7
 
8
- **Verified against:** .NET 10 (SDK 10.0.400-preview; minimal APIs, primary constructors, `Guid.CreateVersion7()`). The "Complete Endpoint Example" block compiles with `TreatWarningsAsErrors` over the `Result`/`ErrorHttpMapping` of `result-pattern.md` (in `ProjectName.Abstractions`/`ProjectName.Extensions`, reached only through the block's own `using`s); the other blocks are documentary. Last-verified: 2026-10-03.
8
+ **Verified against:** .NET 10 (SDK 10.0.400-preview; minimal APIs, primary constructors, `Guid.CreateVersion7()`). The "Complete Endpoint Example" block compiles with `TreatWarningsAsErrors` over the `Result`/`ErrorHttpMapping` of `result-pattern.md` (in `ProjectName.Abstractions`/`ProjectName.Extensions`, reached only through the block's own `using`s); the other blocks are documentary. The "Queue on Postgres" claim ran sequentially on PGlite 0.5.13 (PostgreSQL 18): the eligibility predicate and the lease hold, while lock contention needs the integration test the section requires. Last-verified: 2026-10-30.
9
9
 
10
10
  ---
11
11
 
@@ -230,6 +230,38 @@ specific SQL → dedicated hand-written store + `plan.md` (`## Decisões`) entry
230
230
  (`FromSqlRaw`) unless a query genuinely cannot be expressed in LINQ — and document that
231
231
  in `plan.md` (`## Decisões`).
232
232
 
233
+ ### Queue on Postgres
234
+
235
+ Fire-and-forget and scheduled work go through Hangfire (`backend-integrations-hangfire-hangfire-jobs`).
236
+ A domain queue, whose jobs have state the app reads (progress, attempts, a screen), is a table with a
237
+ claim in a dedicated store. `FOR UPDATE SKIP LOCKED` picks one eligible row and marks it in the same
238
+ statement, so two workers never take the same job:
239
+
240
+ ```sql
241
+ UPDATE jobs
242
+ SET status = 'processing', attempts = attempts + 1, lease_until = now() + interval '2 minutes'
243
+ WHERE id = (
244
+ SELECT id FROM jobs
245
+ WHERE status = 'pending'
246
+ OR (status = 'processing' AND lease_until <= now() AND attempts < 3)
247
+ ORDER BY created_at
248
+ FOR UPDATE SKIP LOCKED
249
+ LIMIT 1
250
+ )
251
+ RETURNING id, attempts;
252
+ ```
253
+
254
+ - **The lease is renewed** while the job runs: a fixed lease expires in the middle of a long job and
255
+ hands it to a second worker.
256
+ - **The attempts ceiling is in the claim**, so a job that always crashes ends as failed instead of
257
+ restarting forever.
258
+ - **Delivery is at-least-once**: a worker can die after the side effect and before the commit. The job
259
+ is idempotent.
260
+ - **An integration test runs the claim against a real Postgres.** Two workers, one job, one winner: a
261
+ fake store proves nothing about locks.
262
+ - **A partial index covers the eligible rows**, and finished jobs leave the table on a schedule. Dead
263
+ rows at the head of the queue are the first thing that degrades a Postgres queue.
264
+
233
265
  ---
234
266
 
235
267
  ## Feature Error Factory
@@ -5,7 +5,7 @@
5
5
  > **Keywords:** hangfire, background job, recurring, fire-and-forget, scheduled
6
6
  > **Load When:** hangfire or background job keywords detected
7
7
 
8
- **Verified against:** .NET 10 (SDK 10.0.400-preview) + Hangfire.Core/Hangfire.AspNetCore 1.8.25 + Hangfire.PostgreSql 1.21.1 + Newtonsoft.Json 13.0.4. The startup, job/handler, dashboard-filter and idempotency blocks compile with `TreatWarningsAsErrors`; the Job Types calls compile inside a wrapper method; the per-job DI scope (`AspNetCoreJobActivator` → `IServiceScopeFactory`) and the `PostgreSqlStorageOptions` defaults were read by reflection on those assemblies; the pooled-endpoint limits (LISTEN/NOTIFY, migrations on the direct string) come from the Neon connection-pooling docs. Cron strings are documentary. Last-verified: 2026-10-03.
8
+ **Verified against:** .NET 10 (SDK 10.0.400-preview) + Hangfire.Core/Hangfire.AspNetCore 1.8.25 + Hangfire.PostgreSql 1.21.1 + Newtonsoft.Json 13.0.4. The startup, job/handler, dashboard-filter and idempotency blocks compile with `TreatWarningsAsErrors`; the Job Types calls compile inside a wrapper method; the per-job DI scope (`AspNetCoreJobActivator` → `IServiceScopeFactory`) and the `PostgreSqlStorageOptions` defaults were read by reflection on those assemblies; the pooled-endpoint limits (LISTEN/NOTIFY, migrations on the direct string) come from the Neon connection-pooling docs. Cron strings are documentary. Scale-to-zero behaviour read from the Neon docs. Last-verified: 2026-10-30.
9
9
 
10
10
  ---
11
11
 
@@ -75,6 +75,7 @@ app.Run();
75
75
  - **Storage on Neon Postgres** (`Hangfire.PostgreSql`), not SQL Server. The `UsePostgreSqlStorage(string, ...)` overloads are `[Obsolete]` in 1.21; use the `options => options.UseNpgsqlConnection(...)` form above.
76
76
  - **`HangfireConnection` points at the direct (unpooled) Neon endpoint**, like migrations (`infrastructure/neon/neon-setup.md`): the storage installs its schema at startup (`PrepareSchemaIfNecessary = true` by default), and Neon recommends the direct string for migrations; and if you turn on `EnableLongPolling` it uses `LISTEN/NOTIFY`, which Neon's PgBouncer (transaction mode) does not support.
77
77
  - Defaults read from `PostgreSqlStorageOptions` 1.21.1: `SchemaName = "hangfire"`, `QueuePollInterval = 15 s`, `InvisibilityTimeout = 30 min`, `DistributedLockTimeout = 10 min`, `EnableLongPolling = false`.
78
+ - **Polling keeps a Neon compute awake.** Neon suspends after 5 minutes without queries, and the 15 s `QueuePollInterval` never lets that happen: a Hangfire server, or any worker polling a queue table, means an always-on compute. Weigh that cost in the plan before adding a worker to a project that relies on scale to zero. `pg_cron` only runs while the compute is active, and an idle `LISTEN` connection also disables scale to zero.
78
79
  - The connection string comes from configuration (user-secrets in development, Coolify environment variables in production), never from code.
79
80
 
80
81
  ---
@@ -5,11 +5,12 @@
5
5
  > **Keywords:** neon auth, better auth, authentication, nextjs, user management, session, JWT, trusted origins, EdDSA, Ed25519, BouncyCastle, signature validator, jwks, middleware runtime, api prefix, data api, postgrest, bypass backend, direct database access
6
6
  > **Load When:** neon auth or authentication keywords detected
7
7
 
8
- **Verified against:** Neon Auth — @neondatabase/auth + @neondatabase/auth-ui (Better Auth). Last-verified: 2026-07-13.
8
+ **Verified against:** Neon Auth — @neondatabase/auth + @neondatabase/auth-ui (Better Auth), run on 2026-07-13; package names of `createNeonAuth`/`createAuthClient` and the install line read from the Neon quickstart for `@neon/auth` 0.8.x, not run. Last-verified: 2026-10-30.
9
+
10
+ > **Pacote novo:** o SDK agora é `@neon/auth` (0.8.1 ou superior), e o produto chama Managed Better Auth. Projeto novo usa ele, com os componentes do Better Auth UI copiados pelo shadcn CLI (`docs/auth/reference/ui-components`). As seções de UI abaixo descrevem `@neondatabase/auth-ui`, que está deprecado: valem para os projetos que ainda o usam.
9
11
 
10
12
  > This standard is the reference implementation, not a troubleshooting log. Every section below
11
- > exists because a real end-to-end run (MORPH_GHLBrain, feature `catalogo-produtos-crud`, task T16,
12
- > 2026-07-13) hit the failure mode it documents. See [Known Failure Modes](#known-failure-modes-postmortem)
13
+ > exists because a real end-to-end run in a production project (2026-07-13) hit the failure mode it documents. See [Known Failure Modes](#known-failure-modes-postmortem)
13
14
  > for the map from bug → section.
14
15
 
15
16
  ---
@@ -78,14 +79,13 @@ should have direct access by default.
78
79
  ### Install Packages
79
80
 
80
81
  ```bash
81
- # Core auth + UI components are separate packages
82
- npm install @neondatabase/auth@latest @neondatabase/auth-ui @neondatabase/serverless
82
+ npm install @neon/auth@latest
83
83
  ```
84
84
 
85
- > The UI components (`AuthView`, `UserButton`, `SignedIn`, `SignedOut`,
86
- > `RedirectToSignIn`) live in `@neondatabase/auth-ui`, not in the core
87
- > `@neondatabase/auth` package. The legacy `@stackframe/stack` packages and
88
- > `@neondatabase/neon-auth-next` are deprecated — do not install them.
85
+ > In projects still on the old packages, the UI components (`AuthView`, `UserButton`,
86
+ > `SignedIn`, `SignedOut`, `RedirectToSignIn`) live in `@neondatabase/auth-ui`. The legacy
87
+ > `@stackframe/stack` packages and `@neondatabase/neon-auth-next` are deprecated — do not
88
+ > install them.
89
89
 
90
90
  ### Environment Variables
91
91
 
@@ -130,7 +130,7 @@ React hooks are re-exported from the client instance for ergonomic imports:
130
130
 
131
131
  ```typescript
132
132
  'use client';
133
- import { createAuthClient } from '@neondatabase/auth/next';
133
+ import { createAuthClient } from '@neon/auth/next';
134
134
  export const authClient = createAuthClient(process.env.NEXT_PUBLIC_AUTH_URL!);
135
135
  export const { useSession } = authClient;
136
136
  ```
@@ -138,7 +138,7 @@ export const { useSession } = authClient;
138
138
  `lib/auth/server.ts`:
139
139
 
140
140
  ```typescript
141
- import { createNeonAuth } from '@neondatabase/auth/next/server';
141
+ import { createNeonAuth } from '@neon/auth/next/server';
142
142
  export const auth = createNeonAuth({
143
143
  baseUrl: process.env.NEON_AUTH_BASE_URL!,
144
144
  cookies: {
@@ -733,8 +733,7 @@ Each Neon branch gets its own isolated auth data. This means:
733
733
 
734
734
  ## Known Failure Modes (Postmortem)
735
735
 
736
- Real bugs hit during the `catalogo-produtos-crud` end-to-end run (MORPH_GHLBrain, task T16,
737
- 2026-07-13). Each one now has a permanent fix in this standard — this table exists so a review can
736
+ Real bugs hit during an end-to-end run in a production project (2026-07-13). Each one now has a permanent fix in this standard — this table exists so a review can
738
737
  scan it in one pass instead of rediscovering these by trial and error.
739
738
 
740
739
  | # | Symptom | Root cause | Fixed in |
@@ -5,7 +5,7 @@
5
5
  > **Keywords:** image, gpt-image, gemini, openrouter, morph-assets, asset, ai-generation, placeholder, mcp, next/image
6
6
  > **Load When:** feature has UI components requiring image assets
7
7
 
8
- **Verified against:** morph-assets MCP (OpenRouter; default model `openai/gpt-image-2.5-flare`) + Next.js 15 `next/image`. Last-verified: 2026-09-27.
8
+ **Verified against:** morph-assets MCP (OpenRouter; default model `openai/gpt-image-2.5-flare`) + Next.js 15 `next/image`. Last-verified: 2026-10-27.
9
9
 
10
10
  ---
11
11
 
@@ -88,6 +88,8 @@ mcp__morph-assets__list_models({ kind: "image", needs?: "refs>=5", task?: string
88
88
 
89
89
  **Cost gate:** every call estimates cost first. Above US$2, over the project budget or with an unknown price, the tool returns `CONFIRMATION_REQUIRED` + `confirmToken`; get human approval and repeat the same call with the token. Use `dryRun: true` before large batches or 4K.
90
90
 
91
+ **Fallback:** when a model is unavailable the tool walks a curated fallback chain (same validation, at most 25 percent more expensive). `noFallback: true` turns it off when the exact model matters.
92
+
91
93
  ## Prompt Engineering Patterns
92
94
 
93
95
  ### Design-System-Aware Prompts
@@ -115,20 +117,47 @@ Describe what the image will BE in the UI, not just the subject:
115
117
 
116
118
  ### Narrative Style
117
119
 
118
- GPT Image, Gemini and MAI all respond best to coherent paragraphs. Build prompts by layering:
120
+ GPT Image, Gemini and MAI all respond best to coherent paragraphs. Build prompts by layering, most important first:
121
+
122
+ 1. **Subject** — hyper-specific description. Not "a warrior in armor" but "a tall elven warrior in ornate silver plate armor with falcon-wing pauldrons and a high collar etched with leaf patterns".
123
+ 2. **Action / pose** — what the subject is doing, expression, gesture.
124
+ 3. **Environment** — foreground, mid-ground, background, described separately. A flat description ("neon city at night") gives a flat image; layers give depth.
125
+ 4. **Lighting** — the biggest quality lever. Multiple sources with different colors and directions: "golden hour key light from the left, cool blue fill from the sky on the right, warm rim light from behind catching the hair"; "harsh overhead spotlight with neon signs reflecting off wet surfaces"; "soft window light as primary, practical warm bulbs adding depth behind". A single flat source gives a flat image.
126
+ 5. **Style** — photography terms (camera, lens, film stock: "85mm portrait lens, shallow depth of field, Kodak Portra 400 grain"), a named illustration style ("cel-shaded", "watercolor", "ink wash", "art nouveau"), or a reference artist when useful ("in the style of Studio Ghibli").
127
+ 6. **Mood reference** — 1 or 2 real-world style references to anchor the look (a product film, an editorial).
128
+ 7. **Color palette** — dominant colors and contrast relationships ("warm amber highlights against deep teal shadows").
129
+ 8. **Composition** — camera angle, framing, negative space, and depth cues: overlapping objects, foreground blur, atmospheric perspective (distant objects fading into haze).
130
+ 9. **Texture and detail** — what makes it tactile: "rain droplets on glass", "worn leather with visible stitching", "dust in a light beam", "condensation on cold metal".
131
+
132
+ Write it as a photo-direction paragraph, not as tags.
133
+
134
+ **Never:** keyword spam ("beautiful, stunning, 4k, masterpiece"), JSON prompts (the models have no structured prompt mode), negative prompts ("no cars"). Describe what is there: "an empty, completely still street" rather than "a street without cars".
135
+
136
+ ### Category Templates
137
+
138
+ Each kind of image has a skeleton that works. Fill the brackets with the layers above.
119
139
 
120
- 1. **Subject** — hyper-specific description
121
- 2. **Environment** — foreground, mid-ground, background (layered for depth)
122
- 3. **Lighting** — multiple sources with different colors/directions
123
- 4. **Style** — photography terms ("85mm portrait lens") or art style ("cel-shaded")
124
- 5. **Color palette** — dominant colors and contrast relationships
125
- 6. **Composition** — camera angle, framing, negative space
140
+ | Category | Skeleton |
141
+ |---|---|
142
+ | Photorealistic | `A [shot type] of [subject with details], [action], set in [environment with atmosphere], illuminated by [lighting], captured with [camera/lens], [mood], [orientation].` |
143
+ | Illustration / sticker | `[Art style] [subject with personality], [key details], [line style] outlines, [shading], [palette], [background].` |
144
+ | Text rendering (logo, infographic, menu) | `[Image type] for [brand or concept context], featuring the text "[exact text]" in [font style], [elements around the text], [color scheme and style], [format].` |
145
+ | Product mockup | `High-resolution studio photograph of [product with materials], on [surface], [lighting setup], [camera angle] for [purpose], sharp focus on [detail], [realism], [ratio].` |
146
+ | Minimalist / negative space | `[Single subject] positioned [placement], [vast background], generous negative space [for what], [subtle light direction], [ratio].` |
147
+ | Sequential art (comic, storyboard) | One generation per panel, same character description repeated verbatim in each. |
126
148
 
127
- **Never:** keyword spam ("beautiful, stunning, 4k, masterpiece"), JSON prompts, negative prompts ("no cars").
149
+ Example (product mockup): "High-resolution studio photograph of a minimalist black ceramic mug with a matte finish on a polished concrete surface, three-point softbox lighting with a subtle warm key light, 45-degree elevated angle for an e-commerce listing, sharp focus on the steam rising from the coffee, photorealistic, square format."
150
+
151
+ ### Prompting Principles
152
+
153
+ - **State the context and the intent.** "Logo for a high-end minimalist skincare brand" beats "make a logo": the model uses the context to make style decisions. Always say what the image is for (social post, site, print).
154
+ - **One concept per generation.** Multi-scene compositions are unreliable. Generate the pieces separately and compose with editing.
155
+ - **Character consistency.** If a character's features drift after several edits, start over with a detailed character description up front.
156
+ - **Set the aspect ratio deliberately**, from the final use (see the guide below).
128
157
 
129
158
  ## Iterative Editing Workflows
130
159
 
131
- **Cardinal rule: generation demands depth; editing demands restraint.**
160
+ **Cardinal rule: generation demands depth; editing demands restraint.** A verbose edit prompt makes the model re-imagine the whole image, and the result looks artificial and over-processed. Keep edits to one or two sentences about exactly what changes, and let the model integrate it. Editing keeps the aspect ratio of the input image; say so explicitly if you need a different one.
132
161
 
133
162
  ### Adding / Removing Elements
134
163
  One sentence. Don't describe how to integrate — the model handles that.
@@ -137,10 +166,11 @@ One sentence. Don't describe how to integrate — the model handles that.
137
166
  ```
138
167
 
139
168
  ### Background Replacement
140
- State the new background simply. Don't over-describe.
169
+ State the new background simply. Don't over-describe: the model matches the lighting on its own.
141
170
  ```
142
171
  "Replace the background with a tropical beach at sunset."
143
172
  ```
173
+ Over-described, and therefore more artificial, not less: "Replace the background with a beautiful tropical beach with golden sand, palm trees swaying, turquoise waves, warm sunset colors reflecting on the water, ensuring the subject's lighting matches the sunset glow".
144
174
 
145
175
  ### Inpainting (Targeted Changes)
146
176
  Name what changes and what stays in one line.
@@ -154,6 +184,13 @@ Name the target style.
154
184
  "Transform this into Van Gogh's Starry Night style."
155
185
  ```
156
186
 
187
+ ### Multi-Image Composition
188
+ Pass the source images in `references` (paths, URLs, library ids, `@brand` / `@brand:name` with a `project`) and refer to them **by order** in the prompt:
189
+ ```
190
+ "Put the woman from the second image in the blue dress from the first image."
191
+ ```
192
+ The reference limit depends on the model (MAI Image Flash takes at most 5; 6 to 14 need Gemini 3.1 Flash Image). Check with `list_models { needs: "refs>=N" }` instead of assuming a fixed limit.
193
+
157
194
  ### Iterative Refinement with `continue_editing`
158
195
  One change at a time:
159
196
  - "Make the lighting warmer"
@@ -179,6 +216,8 @@ Put the exact copy between double quotes in the prompt (`the headline "Summer Sa
179
216
  | Story / reel | `9:16` | Mobile wallpaper, social story, vertical banner |
180
217
  | Standard display | `4:3` | Documentation, slides, standard display |
181
218
 
219
+ **Cards follow the grid, not a fixed ratio.** `3:2` is the default for a landscape card in a row of feature cards or blog headers. Use portrait (`3:4` or `2:3`) when the card is a tall tile (a product or garment shown whole, a team member, a masonry gallery), and square (`1:1`) when the grid is uniform tiles or the same asset doubles as a social post. Generate at the ratio the card renders at, so the `object-fit: cover` crop does not cut the subject.
220
+
182
221
  ## File Naming & Directory
183
222
 
184
223
  **Convention:** `public/images/{feature}/{purpose}.png`
@@ -276,7 +315,7 @@ For a frame that will drive a scroll scrub, also generate at the exact ratio the
276
315
 
277
316
  ## The Two Assets Everyone Forgets
278
317
 
279
- - **Favicon / monogram.** Derive a simple mark (one or two letters, or the existing symbol) that reads at 32x32. Use `edit_image` from the real logo so it stays on-brand. Export PNG at 512x512 and let the browser downscale. **Never invent a new logo**, simplify the existing one.
318
+ - **Favicon / monogram.** Derive a simple mark (one or two letters, or the existing symbol) that reads at 32x32. Use `edit_image` from the real logo so it stays on-brand. Export PNG at 512x512 and let the browser downscale, on a transparent background or a solid brand color, whichever the mark reads better on. **Never invent a new logo**, simplify the existing one.
280
319
  - **OG / share image (1200x630).** The image that appears when the link is pasted into a chat or a feed. Use the strongest shot on the page, with breathing room for a possible brand overlay. Generate or crop at that exact ratio. See `frontend/seo/meta-schema-audit.md`.
281
320
 
282
321
  ## Variations and Cost
@@ -5,7 +5,7 @@
5
5
  > **Keywords:** video generation, seedance, morph-assets, openrouter, img2vid, endImage, directional footage, loop video, E005 moderation, cost ladder, promoteFrom
6
6
  > **Load When:** producing footage for a scroll-driven page or animating a client render
7
7
 
8
- **Verified against:** morph-assets MCP (generate_video, generate_loop_video, get_video, library, media_tool) over OpenRouter; models `bytedance/seedance-1-5-pro` and `bytedance/seedance-2.0`. Last-verified: 2026-09-27.
8
+ **Verified against:** morph-assets MCP (generate_video, generate_loop_video, get_video, library, media_tool) over OpenRouter; models `bytedance/seedance-1-5-pro` and `bytedance/seedance-2.0`. Last-verified: 2026-10-27.
9
9
 
10
10
  ---
11
11
 
@@ -38,7 +38,7 @@ Give a start image and an end image, and let the model interpolate a camera trav
38
38
  1. Pick or generate the **start frame**: the wide shot, the resting state.
39
39
  2. Pick or generate the **end frame** (`endImage`): the destination of the movement (a detail in close-up, a revealed facade).
40
40
  3. Call `generate_video { prompt, image: start, endImage: end }`. The prompt describes the camera movement between the two.
41
- 4. Check the **last-frame QA** in the response (SSIM of the real last frame against `endImage`): ✅ respected, ⚠️ does not match, ≈ inconclusive. A ⚠️ means the model ignored `endImage` and the take will not scrub as designed.
41
+ 4. Check the **last-frame QA** in the response (SSIM of the real last frame against `endImage`): ✅ respected, ⚠️ does not match, ≈ inconclusive. A ⚠️ means the model ignored `endImage` and the take will not scrub as designed: adjust the prompt or the frames and regenerate before showing it to anyone. On ≈, decide by eye from the 6-frame preview the response carries.
42
42
 
43
43
  **Models that respect `endImage` (verified 2026-09-27):** Seedance 1.5 Pro and Seedance 2.0. Use them for scrub.
44
44
 
@@ -107,11 +107,55 @@ TECHNICAL: img2vid with image (start) + endImage (end). No audio. No watermark.
107
107
 
108
108
  ## Section 6: Async Contract
109
109
 
110
- `generate_video` and `generate_loop_video` wait up to `waitSeconds` (default 60) and, if the job is still running, return "⏳ generating" with the job id rather than a file. Poll with `get_video { ids? }` (several ids at once; no ids = every pending job), which downloads the `.mp4` once the job completes. **Never resend `generate_video` while a job is running**: an identical request within 15 minutes returns the same job, but a different one is billed again.
110
+ `generate_video` and `generate_loop_video` wait up to `waitSeconds` and, if the job is still running, return "⏳ generating" with the job id rather than a file. **The default is 60s and the ceiling is 110s** (`DEFAULT_WAIT_S` / `MAX_WAIT_S` in the MCP's `src/tools/video.ts`). A Seedance job takes about 100 to 130s, so with the default it almost always comes back pending: pass `waitSeconds: 110` for Seedance, and even then expect to poll. Poll with `get_video { ids? }` (several ids at once; no ids = every pending job), which downloads the `.mp4` once the job completes. **Never resend `generate_video` while a job is running**: an identical request within 15 minutes returns the same job, but a different one is billed again.
111
111
 
112
112
  `library { op: "pending" | "list" | "info" }` reads local history and spends no credits (it replaces the old `list_videos`). `usage { op: "balance" }` checks the OpenRouter key without spending credits, which makes it the safe health check for the MCP.
113
113
 
114
- Post-production runs locally with no cost through `media_tool` (ffmpeg): `scrub_frames` (frames + `frames.json`, each re-extraction creates a new `v{N}` folder, which doubles as the cache-bust), `poster` (static image for mobile), `flip` (hflip when the subject collides with the copy), `concat`, `compress` (web background) and `picker` (the side-by-side approval page for the gate).
114
+ Post-production runs locally with no cost through `media_tool` (ffmpeg): `scrub_frames` (frames + `frames.json`, each re-extraction creates a new `v{N}` folder, which doubles as the cache-bust), `poster` (static image for mobile), `flip` (hflip when the subject collides with the copy), `concat`, `compress` (web background) and `picker` (the side-by-side approval page for the gate). For an ambient loop, `compress { format: "webm", width: 1280 }` with no audio is usually enough: a test loop went from 421 KB to 52 KB (upstream `morph-lp-cinematic` skill; not re-measured here).
115
+
116
+ ---
117
+
118
+ ## Section 7: Seamless Loop Prompt (Ambient Plane)
119
+
120
+ The special case from Section 1: a background video that plays itself on repeat, never the scroll-driven hero. Generate it with `generate_loop_video { prompt, image }`, which sends the same image as first and last frame. The prompt has to describe a complete cycle that returns to its start, and it has to be exhaustive about the details people leave vague: camera, lighting, timing, text and the loop point.
121
+
122
+ **Infer the loop direction from the subject** when the brief does not give one:
123
+
124
+ | Subject type | Default direction | Why |
125
+ |---|---|---|
126
+ | Mechanical, tech, electronics | Deconstruction reveal | shows engineering, internals, precision |
127
+ | Vehicles, transportation | Motion flythrough or speed orbit | movement and power |
128
+ | Fashion, wearables, accessories | Slow orbit focused on material and texture | craftsmanship and detail |
129
+ | Food, beverage | Ingredient explosion or pour sequence | freshness, composition |
130
+ | Abstract, software, SaaS | Particle morph or data visualization | makes the intangible visible |
131
+ | Architecture, real estate | Cinematic flythrough or reveal | space and scale |
132
+ | Beauty, skincare | Liquid or texture flow | sensory qualities |
133
+
134
+ With the subject known, write the prompt; only a missing subject justifies a question, and one question is enough.
135
+
136
+ **Seven sections, none skipped:**
137
+
138
+ 1. **SCENE.** The subject with material, color, form and key details ("matte black over-ear headphones with brushed aluminum hinges", never "headphones"); the environment around it (void, studio, abstract space); 1 or 2 real-world mood references (a product film, a director's style); the palette (background, accents, highlights).
139
+ 2. **CAMERA.** Motion type, speed, elevation, angle, distance, focal or depth-of-field notes. **Start position equals end position**: state the rotation in degrees ("one continuous 360-degree horizontal orbit at a fixed 15-degree elevation").
140
+ 3. **ACTION ARC.** The starting state; the transformation, granular and in order; the **peak** (maximum separation, closest zoom, most dramatic reveal) called out explicitly at roughly 40 to 60 percent of the duration; and the return, described as an intentional resolve ("components drift back and click into place"), not "it reverses". Slow build, peak, satisfying resolution.
141
+ 4. **TEXT CHOREOGRAPHY.** With headlines, for each one: the exact quoted string, the entrance relative to the action arc, position (upper third, center, lower third, aligned left or right), size (large and dominant), contrast (against the darkest or calmest region of the frame, with a darkening gradient or clear zone if the background is busy), exit, and entrance motion. Without headlines write exactly: "No text. No UI. Background plate only." **Readability wins**: if an effect hurts legibility, the effect yields.
142
+ 5. **LIGHTING & ATMOSPHERE.** Source, type (key, rim, ambient), color temperature, which edges the rim catches, atmospheric effects (particles, dust, bokeh). Lighting is **static** for the whole clip, with no shift, flicker or temperature change. Particle density and distribution are identical at the start and the end.
143
+ 6. **LOOP SEAL.** Same reference image for first and last frame; camera back at its exact start; every moving element (components, particles, effects) back in its starting state; no residual motion or lighting drift at the loop point; the return feels intentional (a click, a settle), never abrupt.
144
+ 7. **TECHNICAL.** Duration, seamless loop, image-to-video with the same first and last frame, no audio, no watermark. Resolution follows the cost ladder of Section 4 (the MCP renders 480p or 1080p, never 4K). **The duration in the prompt has to match the `duration` of the call**: the MCP defaults to 5s (`DEFAULT_DURATION` in `src/tools/video.ts`), so a prompt choreographed for 10s and sent without `duration: 10` comes out compressed into 5s, peak and text included. The accepted durations come from the OpenRouter catalog per model (`list_models` shows them; Seedance 2.0 takes 4 to 15s), and an out-of-range value is snapped to the nearest one with a warning.
145
+
146
+ **Craft rules.** Specificity over vagueness: exact angles, speeds, degrees and percentages. Describe what happens on screen, never editing software ("the frame scales inward rapidly", not "apply a keyframed scale in After Effects"). Direct, technical tone, like a director's shot notes, with no hype words ("stunning", "breathtaking", "mesmerizing"). Bullets inside each section for scanability.
147
+
148
+ A short, neutral example of the level of detail expected (a background plate with no text):
149
+
150
+ ```
151
+ SCENE: a brushed-steel wristwatch with a sapphire crystal floats in a deep charcoal void; sparse silver dust drifts behind it. Mood: macro product film, precision-engineering reveal. Palette: charcoal background, cool steel, one warm amber highlight on the crown.
152
+ CAMERA: one continuous 360-degree horizontal orbit at a fixed 10-degree elevation, slow and unhurried, 85mm macro feel, shallow depth of field. Ends at its exact starting angle and distance.
153
+ ACTION ARC: the watch at rest; the case back opens and the movement lifts out in layers; peak at 50% with every layer separated; the layers settle back and the case closes with a soft click.
154
+ TEXT CHOREOGRAPHY: No text. No UI. Background plate only.
155
+ LIGHTING & ATMOSPHERE: cool key light from the upper left, thin warm rim on the right edge, static for the whole clip; dust density identical at start and end.
156
+ LOOP SEAL: same reference image as first and last frame; camera, layers and dust back to their starting state; no residual motion at the loop point.
157
+ TECHNICAL: 10s seamless loop, image-to-video with identical first and last frame, no audio, no watermark.
158
+ ```
115
159
 
116
160
  ---
117
161
 
@@ -126,6 +170,8 @@ Post-production runs locally with no cost through `media_tool` (ffmpeg): `scrub_
126
170
  - [ ] Prompt reserves a calm region for copy, subject opposite the text
127
171
  - [ ] Draft at 480p approved before any 1080p render, and the final made with `promoteFrom`
128
172
  - [ ] Frames re-extracted with a bumped cache-bust after the final render
173
+ - [ ] Seedance calls made with `waitSeconds: 110`; a pending job polled with `get_video`, never resent
174
+ - [ ] Ambient loop prompt carries all seven sections, static lighting and an explicit loop seal, and the call passes the same `duration` the prompt was written for
129
175
 
130
176
  ## Boundary: Runtime Generation Is Another Standard
131
177
 
@@ -15,7 +15,7 @@ A design that follows the tokens, has every state and passes WCAG can still be f
15
15
 
16
16
  ## Section 1: Write the Direction First
17
17
 
18
- Before the first component, answer four questions in `1-design/direcao.md`:
18
+ Before the first component, answer four questions; the chosen direction goes in `**Direção escolhida:**` of `1-design/mockups.md`:
19
19
 
20
20
  1. **Concept in one sentence.** What the screen says, not what it contains. *"The login tells the product cycle in scenes while you sign in."*
21
21
  2. **Visual metaphor.** The image that organizes the layout. *Stories; a control room; a ticket being stamped; a garden that grows.*
@@ -80,7 +80,7 @@ The direction is proved in a rendered prototype, not in prose: `1-design/prototi
80
80
 
81
81
  ## Checklist
82
82
 
83
- - [ ] `direcao.md` answers concept, metaphor, memorable, must-not-look-like
83
+ - [ ] The direction answers concept, metaphor, memorable, must-not-look-like
84
84
  - [ ] Two or three distinct directions for a new screen or brand moment, one recommended with a reason
85
85
  - [ ] Fewer than three template tells on the final screen
86
86
  - [ ] Mobile composed at 390 px on its own terms
@@ -5,7 +5,7 @@
5
5
  > **Keywords:** frame-scrub, canvas, scroll-driven video, scrubbing, ffmpeg, apple-style, poster fallback
6
6
  > **Load When:** ui-designer active, or the brief says scroll controls a video
7
7
 
8
- **Verified against:** GSAP 3.13 ScrollTrigger + Lenis 1.x + ffmpeg 6.x, shipped to production. Last-verified: 2026-08-21.
8
+ **Verified against:** GSAP 3.13 ScrollTrigger + Lenis 1.x + ffmpeg 6.x, shipped to production. Last-verified: 2026-10-27.
9
9
 
10
10
  ---
11
11
 
@@ -51,7 +51,17 @@ const framePath = (i) => `/hero-frames/v${manifest.version}/frame_${String(i).pa
51
51
 
52
52
  Source: the upstream `morph-lp-cinematic` skill (build-stack reference); not re-measured here.
53
53
 
54
- **Treat total sequence weight as a performance budget.** It shows up directly in Lighthouse. If it is too heavy, cut frame count (120 instead of 145) or resolution before cutting quality.
54
+ **Footage from an AI video model is 24 fps, not 30.** A 4s Seedance clip comes out with about 97 frames, which already sits in the useful range: 100 to 145 frames scrubs smoothly without getting heavy. Keep the source frame rate unless the sequence is too light to scrub smoothly. In the upstream test, 97 WebP frames at 1080p weighed about 2 MB in total; `scrub_frames` warns above 25 MB, which is too heavy for a hero. Source: the upstream `morph-lp-cinematic` skill; not re-measured here.
55
+
56
+ **Treat total sequence weight as a performance budget.** It shows up directly in Lighthouse. If it is too heavy, cut frame count (120 instead of 145), width (1280) or quality before cutting the sequence length.
57
+
58
+ **Manual WebP extraction that matches the manifest layout.** When `scrub_frames` is unavailable, extract into the same `v{N}/frame_%04d` structure so the manifest-driven `framePath` above keeps resolving:
59
+
60
+ ```bash
61
+ ffmpeg -i hero.mp4 -vf "fps=24,scale=1920:-1" -c:v libwebp -quality 80 v1/frame_%04d.webp
62
+ ```
63
+
64
+ Prepend `hflip,` to the `-vf` chain to mirror, and bump the folder (`v2/`, `v3/`) on every re-extraction.
55
65
 
56
66
  **Mirror the footage when the subject collides with the copy.** If the subject sits on the side where text goes, flip it during extraction:
57
67