@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.
- package/CLAUDE.md +12 -10
- package/README.md +4 -4
- package/bin/morph-spec.js +40 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +28 -36
- package/docs/QUICKSTART.md +31 -191
- package/framework/CLAUDE.md +24 -30
- package/framework/MORPH.md +47 -123
- package/framework/agents.json +6 -374
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-archive.md +4 -3
- package/framework/commands/morph-hotfix.md +8 -30
- package/framework/commands/morph-infra.md +2 -2
- package/framework/commands/morph-media.md +10 -9
- package/framework/commands/morph-preflight.md +3 -3
- package/framework/commands/morph-review.md +15 -0
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +84 -95
- package/framework/evals/review-checklist.md +45 -53
- package/framework/hooks/README.md +4 -4
- package/framework/hooks/claude-code/core/README.md +1 -1
- package/framework/hooks/claude-code/core/denylist.json +4 -4
- package/framework/hooks/claude-code/core/gate-guard.js +9 -0
- package/framework/hooks/claude-code/core/masked-exit.js +49 -0
- package/framework/hooks/claude-code/core/state-sync.js +4 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +1 -2
- package/framework/hooks/claude-code/core/test-budget.js +10 -70
- package/framework/hooks/claude-code/core/validate-completion.js +11 -24
- package/framework/hooks/claude-code/core/worktree-context.js +5 -22
- package/framework/hooks/claude-code/core/worktree-create.js +6 -45
- package/framework/hooks/claude-code/core/worktree-env-inject.js +2 -10
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/gate-event.js +2 -2
- package/framework/hooks/shared/graph-refresh.js +20 -3
- package/framework/hooks/shared/heavy-command.js +32 -1
- package/framework/hooks/shared/heavy-config.js +1 -1
- package/framework/hooks/shared/next-step.js +63 -24
- package/framework/hooks/shared/phase-utils.js +6 -9
- package/framework/hooks/shared/report-progress.js +86 -0
- package/framework/hooks/shared/state-reader.js +3 -2
- package/framework/hooks/statusline.js +332 -0
- package/framework/mcp/registry.json +4 -4
- package/framework/phases.json +32 -10
- package/framework/rules/ai-agents-standards.md +10 -2
- package/framework/rules/csharp-standards.md +4 -0
- package/framework/rules/data-standards.md +1 -1
- package/framework/rules/ef-standards.md +20 -0
- package/framework/rules/frontend-standards.md +1 -1
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/implement-report.md +25 -0
- package/framework/rules/infrastructure-standards.md +2 -0
- package/framework/rules/neon-auth-standards.md +1 -1
- package/framework/rules/neon-standards.md +3 -3
- package/framework/rules/nextjs-standards.md +7 -0
- package/framework/rules/plan-grammar.md +53 -0
- package/framework/rules/ui-design.md +27 -0
- package/framework/schemas/config.schema.json +11 -18
- package/framework/schemas/feature.schema.json +7 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +2 -2
- package/framework/skills/core/morph-apply/SKILL.md +39 -166
- package/framework/skills/core/morph-apply/references/debugging.md +8 -0
- package/framework/skills/core/morph-apply/references/hotfix.md +42 -0
- package/framework/skills/core/morph-apply/references/machine.md +29 -0
- package/framework/skills/core/{morph-media/SKILL.md → morph-apply/references/media.md} +11 -74
- package/framework/skills/core/morph-apply/references/review.md +65 -0
- package/framework/skills/core/morph-plan/SKILL.md +49 -109
- package/framework/skills/core/morph-plan/references/critique.md +43 -0
- package/framework/skills/core/morph-plan/references/interview.md +37 -0
- package/framework/skills/core/morph-uiux/SKILL.md +47 -64
- package/framework/skills/core/morph-uiux/surfaces.json +32 -0
- package/framework/standards/STANDARDS.json +51 -28
- package/framework/standards/STANDARDS.meta.json +16 -19
- package/framework/standards/ai-agents/agent-session.md +2 -3
- package/framework/standards/ai-agents/context-providers.md +2 -2
- package/framework/standards/ai-agents/durable-workflows-hitl.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +2 -2
- package/framework/standards/ai-agents/rag-custom-pgvector.md +7 -6
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/ai-agents/tool-adapters.md +2 -2
- package/framework/standards/architecture/stack-baseline.md +70 -0
- package/framework/standards/backend/database/postgresql/database.md +22 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +33 -1
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +2 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +12 -13
- package/framework/standards/frontend/design-system/ai-image-generation.md +51 -12
- package/framework/standards/frontend/design-system/ai-video-generation.md +50 -4
- package/framework/standards/frontend/design-system/art-direction.md +2 -2
- package/framework/standards/frontend/scroll-driven/frame-scrub.md +12 -2
- package/framework/standards/frontend/scroll-driven/scroll-components.md +76 -4
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +2 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +62 -20
- package/framework/standards/infrastructure/neon/neon-platform.md +61 -0
- package/framework/standards/infrastructure/neon/neon-setup.md +14 -13
- package/framework/standards/integration/mcp/mcp-tools.md +20 -20
- package/framework/templates/REGISTRY.json +1 -2
- package/framework/templates/context/ARCHITECTURE.md +1 -1
- package/framework/templates/docs/plan.md +15 -12
- package/framework/templates/docs/ui-mockups.md +17 -105
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +1 -1
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +88 -23
- package/src/commands/graph/diff.js +74 -0
- package/src/commands/graph/status.js +1 -1
- package/src/commands/observability/cost.js +2 -4
- package/src/commands/observability/telemetry.js +1 -1
- package/src/commands/project/doctor.js +23 -39
- package/src/commands/project/fleet.js +5 -6
- package/src/commands/project/init.js +63 -36
- package/src/commands/project/stack-scan.js +3 -42
- package/src/commands/project/stack-set.js +26 -0
- package/src/commands/project/status.js +12 -13
- package/src/commands/project/update.js +39 -3
- package/src/commands/project/worktree.js +3 -14
- package/src/commands/state/advance.js +17 -17
- package/src/commands/state/approve.js +0 -6
- package/src/commands/state/archive.js +27 -1
- package/src/commands/state/auto-approval.js +2 -2
- package/src/commands/state/create.js +1 -6
- package/src/commands/state/gate-check.js +38 -31
- package/src/commands/state/gate-decision.js +3 -2
- package/src/commands/state/next.js +1 -0
- package/src/commands/state/recap.js +67 -0
- package/src/commands/state/report-check.js +36 -178
- package/src/commands/validation/plan-check.js +16 -11
- package/src/commands/validation/validate-feature.js +3 -3
- package/src/commands/validation/verify.js +3 -6
- package/src/core/paths/output-schema.js +3 -3
- package/src/core/state/state-manager.js +2 -4
- package/src/dashboard/app/components/CommandPalette.jsx +2 -5
- package/src/dashboard/app/components/GatePauses.jsx +116 -0
- package/src/dashboard/app/components/SessionMap.jsx +77 -0
- package/src/dashboard/app/pages/Activity.jsx +4 -0
- package/src/dashboard/app/pages/AgentMap.jsx +1 -2
- package/src/dashboard/app/pages/FeatureDetail.jsx +129 -186
- package/src/dashboard/app/pages/Features.jsx +7 -7
- package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
- package/src/dashboard/app/pages/Portfolio.jsx +40 -5
- package/src/dashboard/scanner/activity-scanner.js +29 -0
- package/src/dashboard/scanner/agents-scanner.js +11 -14
- package/src/dashboard/scanner/features-scanner.js +45 -24
- package/src/dashboard/scanner/project-api.js +11 -2
- package/src/dashboard/scanner/skills-scanner.js +11 -1
- package/src/lib/detectors/index.js +1 -1
- package/src/lib/e2e/applicability.js +3 -1
- package/src/lib/git/tree-state.js +1 -16
- package/src/lib/graph/diff-tree.js +127 -0
- package/src/lib/graph/graphify-runtime.js +12 -5
- package/src/lib/installers/memory-installer.js +3 -3
- package/src/lib/installers/rules-installer.js +57 -0
- package/src/lib/installers/stack-assets.js +17 -0
- package/src/lib/installers/stack-sync.js +219 -0
- package/src/lib/plan/checks.js +61 -109
- package/src/lib/plan/critique.js +28 -0
- package/src/lib/plan/media.js +42 -0
- package/src/lib/plan/plan-html.js +125 -0
- package/src/lib/plan/plan-parser.js +34 -45
- package/src/lib/recap/piper.js +36 -0
- package/src/lib/recap/recap.js +232 -0
- package/src/lib/review-reports.js +28 -4
- package/src/lib/risk-detector.js +90 -11
- package/src/lib/specs/screen-checklist.js +33 -48
- package/src/lib/stack/dotnet-build-ignores.js +8 -9
- package/src/lib/telemetry/metrics.js +27 -29
- package/src/lib/validators/reuse/reuse-validator.js +1 -9
- package/src/lib/validators/verify-runner.js +0 -7
- package/src/lib/worktree/fleet-scanner.js +7 -20
- package/src/lib/worktree/worktree-audit.js +0 -37
- package/src/scripts/setup-infra.js +15 -50
- package/src/utils/agents-installer.js +27 -201
- package/src/utils/hooks-installer.js +11 -95
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +0 -23
- package/framework/hooks/claude-code/statusline.py +0 -699
- package/framework/hooks/claude-code/statusline.sh +0 -11
- package/framework/hooks/shared/agent-reap.js +0 -74
- package/framework/hooks/shared/slice-progress.js +0 -110
- package/framework/skills/core/morph-hotfix/SKILL.md +0 -133
- package/framework/skills/core/morph-machine/SKILL.md +0 -46
- package/framework/skills/core/morph-review/SKILL.md +0 -129
- package/framework/skills/core/morph-standards/SKILL.md +0 -52
- package/framework/standards/PROVENANCE.json +0 -119
- package/framework/standards/infrastructure/neon/neon-pgrag.md +0 -243
- package/src/commands/project/worktree-reap.js +0 -34
- package/src/commands/state/handoff.js +0 -65
- package/src/commands/validation/mutate.js +0 -263
- package/src/commands/validation/scout-check.js +0 -28
- package/src/lib/generators/handoff-generator.js +0 -172
- package/src/lib/mutation/adapters.js +0 -79
- package/src/lib/mutation/commands.js +0 -61
- package/src/lib/mutation/dotnet-introspection.js +0 -281
- package/src/lib/mutation/manifest.js +0 -144
- package/src/lib/mutation/plan.js +0 -44
- package/src/lib/mutation/report.js +0 -68
- package/src/lib/mutation/runner.js +0 -301
- package/src/lib/mutation/schemata.js +0 -60
- package/src/lib/mutation/snapshot.js +0 -65
- package/src/lib/mutation/subset.js +0 -109
- package/src/lib/mutation/trx.js +0 -50
- package/src/lib/scout/scout-check.js +0 -293
- package/src/lib/validators/traceability-check.js +0 -103
- 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`
|
|
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
|
|
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. **
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
82
|
-
npm install @neondatabase/auth@latest @neondatabase/auth-ui @neondatabase/serverless
|
|
82
|
+
npm install @neon/auth@latest
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
>
|
|
86
|
-
> `RedirectToSignIn`) live in `@neondatabase/auth-ui
|
|
87
|
-
> `@
|
|
88
|
-
>
|
|
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 '@
|
|
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 '@
|
|
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
|
|
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-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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-
|
|
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`
|
|
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/
|
|
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
|
-
- [ ]
|
|
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-
|
|
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
|
-
**
|
|
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
|
|