@polymorphism-tech/morph-spec 10.0.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 +2 -0
- package/README.md +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +28 -36
- package/docs/QUICKSTART.md +31 -191
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +2 -2
- package/framework/commands/morph-preflight.md +3 -3
- package/framework/docs/CLI.md +1 -1
- package/framework/hooks/shared/next-step.js +2 -2
- package/framework/mcp/registry.json +4 -4
- package/framework/rules/ai-agents-standards.md +1 -1
- package/framework/rules/data-standards.md +1 -1
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-auth-standards.md +1 -1
- package/framework/rules/neon-standards.md +3 -3
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +4 -3
- package/framework/skills/core/morph-plan/SKILL.md +19 -13
- package/framework/skills/core/morph-plan/references/critique.md +11 -8
- package/framework/skills/core/morph-plan/references/interview.md +18 -6
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +20 -24
- package/framework/standards/STANDARDS.meta.json +12 -19
- package/framework/standards/ai-agents/rag-custom-pgvector.md +1 -0
- package/framework/standards/architecture/stack-baseline.md +26 -3
- 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 +10 -9
- 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/docs/plan.md +4 -1
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +1 -1
- package/package.json +1 -1
- package/src/commands/state/auto-approval.js +1 -1
- package/src/commands/state/create.js +1 -1
- package/src/commands/state/gate-check.js +2 -2
- package/src/lib/detectors/index.js +1 -1
- package/src/lib/installers/memory-installer.js +1 -1
- package/src/lib/plan/checks.js +8 -1
- package/framework/standards/infrastructure/neon/neon-pgrag.md +0 -243
|
@@ -5,7 +5,9 @@
|
|
|
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
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)
|
|
@@ -77,14 +79,13 @@ should have direct access by default.
|
|
|
77
79
|
### Install Packages
|
|
78
80
|
|
|
79
81
|
```bash
|
|
80
|
-
|
|
81
|
-
npm install @neondatabase/auth@latest @neondatabase/auth-ui @neondatabase/serverless
|
|
82
|
+
npm install @neon/auth@latest
|
|
82
83
|
```
|
|
83
84
|
|
|
84
|
-
>
|
|
85
|
-
> `RedirectToSignIn`) live in `@neondatabase/auth-ui
|
|
86
|
-
> `@
|
|
87
|
-
>
|
|
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.
|
|
88
89
|
|
|
89
90
|
### Environment Variables
|
|
90
91
|
|
|
@@ -129,7 +130,7 @@ React hooks are re-exported from the client instance for ergonomic imports:
|
|
|
129
130
|
|
|
130
131
|
```typescript
|
|
131
132
|
'use client';
|
|
132
|
-
import { createAuthClient } from '@
|
|
133
|
+
import { createAuthClient } from '@neon/auth/next';
|
|
133
134
|
export const authClient = createAuthClient(process.env.NEXT_PUBLIC_AUTH_URL!);
|
|
134
135
|
export const { useSession } = authClient;
|
|
135
136
|
```
|
|
@@ -137,7 +138,7 @@ export const { useSession } = authClient;
|
|
|
137
138
|
`lib/auth/server.ts`:
|
|
138
139
|
|
|
139
140
|
```typescript
|
|
140
|
-
import { createNeonAuth } from '@
|
|
141
|
+
import { createNeonAuth } from '@neon/auth/next/server';
|
|
141
142
|
export const auth = createNeonAuth({
|
|
142
143
|
baseUrl: process.env.NEON_AUTH_BASE_URL!,
|
|
143
144
|
cookies: {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Keywords:** docker compose, worktree, ports, container_name, POSTGRES_PORT, COMPOSE_PROJECT_NAME, local dev, e2e stack
|
|
6
6
|
> **Load When:** a compose file, a dev-server port, or a worktree stack is being written or debugged
|
|
7
7
|
|
|
8
|
-
**Verified against:** Docker Compose v2 + morph-spec 8.34 (dedicated stack per worktree). Last-verified: 2026-
|
|
8
|
+
**Verified against:** Docker Compose v2 + morph-spec 8.34 (dedicated stack per worktree). The `pgvector/pgvector:pg17` tag was confirmed on Docker Hub; the image was not run. Last-verified: 2026-10-30.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -86,7 +86,7 @@ services:
|
|
|
86
86
|
timeout: 5s
|
|
87
87
|
retries: 12
|
|
88
88
|
postgres:
|
|
89
|
-
image:
|
|
89
|
+
image: pgvector/pgvector:pg17
|
|
90
90
|
ports:
|
|
91
91
|
- "${POSTGRES_PORT:-5432}:5432"
|
|
92
92
|
environment:
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> **Scope:** neon, dotnet-vsa
|
|
4
4
|
> **Layer:** 1 (on scope)
|
|
5
|
-
> **Keywords:** pgvector, vector, embeddings, similarity search, HNSW, cosine
|
|
5
|
+
> **Keywords:** pgvector, vector, embeddings, similarity search, HNSW, cosine, hybrid search, busca híbrida, RRF, BM25, full-text, lakebase, pg_search
|
|
6
6
|
> **Load When:** pgvector or embedding keywords detected
|
|
7
7
|
|
|
8
|
-
**Verified against:** .NET 10 (SDK 10.0.400-preview) + Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + Pgvector.EntityFrameworkCore 0.3.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08). The entity, configuration, DbContext, search-handler and raw-Npgsql blocks compile with `TreatWarningsAsErrors` (handler over the `Result`/`IHandler` of `result-pattern.md`/`vertical-slice.md`), and the handler's query shape was translated with `ToQueryString()` to `ORDER BY d.embedding <=> @query`; the SQL (index DDL, `ef_search`,
|
|
8
|
+
**Verified against:** .NET 10 (SDK 10.0.400-preview) + Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + Pgvector.EntityFrameworkCore 0.3.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08). The entity, configuration, DbContext, search-handler and raw-Npgsql blocks compile with `TreatWarningsAsErrors` (handler over the `Result`/`IHandler` of `result-pattern.md`/`vertical-slice.md`), and the handler's query shape was translated with `ToQueryString()` to `ORDER BY d.embedding <=> @query`; the SQL (index DDL, `ef_search`, `match_documents`) is documentary, checked against the pgvector README; `hybrid_search` ran on PGlite 0.5.13, not on a Neon compute; the `unaccent` dictionary in a text search configuration is what the Neon `unaccent` page describes (only `unaccent.rules` is closed); Lakebase Search read from the Neon docs. Last-verified: 2026-10-30.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -218,38 +218,80 @@ $$;
|
|
|
218
218
|
|
|
219
219
|
## Hybrid Search (Vector + Full-Text)
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
Semantic search alone misses exact terms (SKU, name, code, error message). Keyword search alone misses paraphrases. Run both and fuse the **ranks** with Reciprocal Rank Fusion: cosine distance and text rank live on different scales, so a weighted sum of the two scores is meaningless. Same rule as above: a SQL function, justified in `plan.md` (`## Decisões`).
|
|
222
|
+
|
|
223
|
+
The default runs on Neon and on the local Docker Postgres alike: native full-text search over a generated `tsvector` with a GIN index. Computing `to_tsvector` inline in the query never uses the index. Content in pt-BR needs its own configuration, or `devolucao` never matches `devolução`:
|
|
224
|
+
|
|
225
|
+
```sql
|
|
226
|
+
CREATE EXTENSION IF NOT EXISTS unaccent;
|
|
227
|
+
|
|
228
|
+
CREATE TEXT SEARCH CONFIGURATION pt_unaccent (COPY = portuguese);
|
|
229
|
+
ALTER TEXT SEARCH CONFIGURATION pt_unaccent
|
|
230
|
+
ALTER MAPPING FOR hword, hword_part, word WITH unaccent, portuguese_stem;
|
|
231
|
+
|
|
232
|
+
ALTER TABLE documents ADD COLUMN search_tsv tsvector
|
|
233
|
+
GENERATED ALWAYS AS (to_tsvector('pt_unaccent', title || ' ' || content)) STORED;
|
|
234
|
+
|
|
235
|
+
CREATE INDEX idx_documents_search_tsv ON documents USING gin (search_tsv);
|
|
236
|
+
```
|
|
222
237
|
|
|
223
238
|
```sql
|
|
224
239
|
CREATE OR REPLACE FUNCTION hybrid_search(
|
|
225
240
|
query_text text,
|
|
226
241
|
query_embedding vector(1536),
|
|
227
242
|
match_count int DEFAULT 10,
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
) RETURNS TABLE (id uuid, title text, content text, score
|
|
243
|
+
candidate_count int DEFAULT 50,
|
|
244
|
+
rrf_k int DEFAULT 60
|
|
245
|
+
) RETURNS TABLE (id uuid, title text, content text, score double precision)
|
|
231
246
|
LANGUAGE sql STABLE AS $$
|
|
232
247
|
WITH vector_results AS (
|
|
233
|
-
SELECT id,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
248
|
+
SELECT c.id, row_number() OVER (ORDER BY c.distance) AS rank
|
|
249
|
+
FROM (
|
|
250
|
+
SELECT d.id, d.embedding <=> query_embedding AS distance
|
|
251
|
+
FROM documents d
|
|
252
|
+
ORDER BY distance
|
|
253
|
+
LIMIT candidate_count
|
|
254
|
+
) c
|
|
237
255
|
),
|
|
238
256
|
text_results AS (
|
|
239
|
-
SELECT id,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
257
|
+
SELECT c.id, row_number() OVER (ORDER BY c.text_score DESC) AS rank
|
|
258
|
+
FROM (
|
|
259
|
+
SELECT d.id, ts_rank_cd(d.search_tsv, q.query) AS text_score
|
|
260
|
+
FROM documents d, websearch_to_tsquery('pt_unaccent', query_text) AS q(query)
|
|
261
|
+
WHERE d.search_tsv @@ q.query
|
|
262
|
+
ORDER BY text_score DESC
|
|
263
|
+
LIMIT candidate_count
|
|
264
|
+
) c
|
|
265
|
+
),
|
|
266
|
+
fused AS (
|
|
267
|
+
SELECT COALESCE(v.id, t.id) AS id,
|
|
268
|
+
COALESCE(1.0 / (rrf_k + v.rank), 0) + COALESCE(1.0 / (rrf_k + t.rank), 0) AS score
|
|
269
|
+
FROM vector_results v
|
|
270
|
+
FULL OUTER JOIN text_results t ON v.id = t.id
|
|
244
271
|
)
|
|
245
|
-
SELECT
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
ORDER BY score DESC
|
|
272
|
+
SELECT d.id, d.title, d.content, f.score::double precision
|
|
273
|
+
FROM fused f
|
|
274
|
+
JOIN documents d ON d.id = f.id
|
|
275
|
+
ORDER BY f.score DESC
|
|
276
|
+
LIMIT match_count;
|
|
249
277
|
$$;
|
|
250
278
|
```
|
|
251
279
|
|
|
252
|
-
|
|
280
|
+
- Multi-tenant table: filter the tenant in **both** inner queries, never only after the fusion.
|
|
281
|
+
- `websearch_to_tsquery` accepts raw user text. `to_tsquery` errors on plain phrases.
|
|
282
|
+
- Both the SQL above and its behaviour (accent-insensitive match, exact code ranked first, empty keyword branch) were run on PGlite 0.5.13 (PostgreSQL 18, pgvector 0.8.7).
|
|
283
|
+
|
|
284
|
+
### Lakebase Search (Neon-only upgrade)
|
|
285
|
+
|
|
286
|
+
`pg_search` (ParadeDB) is gone from Neon: installs blocked on 2026-03-19, removed on 2026-09-21. Its replacement is `lakebase_text` (PostgreSQL 16+), which adds real BM25 ranking on the same `tsvector` column. `lakebase_vector` adds the `lakebase_ann` index over the same `vector` columns for scale. Neither exists in the local Docker Postgres, so adopting one is an ADR in `plan.md` (`## Decisões`) that says how dev and tests run. Reach for them only when an eval shows `ts_rank_cd` ranks badly, or the corpus outgrows HNSW.
|
|
287
|
+
|
|
288
|
+
Traps of `lakebase_text`:
|
|
289
|
+
|
|
290
|
+
- `<@>` only scores: filter with `@@` in the `WHERE`, or every row comes back.
|
|
291
|
+
- The score is negative: `ORDER BY score` ascending, best first. RRF is unaffected, since it reads ranks.
|
|
292
|
+
- `to_bm25query` takes the index name, because the corpus statistics live in the index.
|
|
293
|
+
- Create the index after the initial load and run `VACUUM` after bulk loads. The statistics are not MVCC-versioned, so scores can shift under a concurrent `VACUUM`.
|
|
294
|
+
- There is no fuzzy match (`pg_trgm` covers typos) and no `paradedb.snippet()` (`ts_headline` covers highlighting).
|
|
253
295
|
|
|
254
296
|
## Npgsql Vector Type Handler
|
|
255
297
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Neon — o que a plataforma entrega pronto
|
|
2
|
+
|
|
3
|
+
> **Scope:** neon
|
|
4
|
+
> **Layer:** 1 (resumo injetado pela rule `neon-standards`; lido no plano antes de contratar serviço novo)
|
|
5
|
+
> **Keywords:** neon, plataforma, branch, snapshot, PITR, restore, object storage, s3, ai gateway, read replica, slow query, query tuning, ip allow, protected branch, região, neon cli
|
|
6
|
+
> **Load When:** projeto com `neon` precisa de arquivo, gateway de LLM, ambiente descartável, rede de segurança de migration, diagnóstico de consulta ou leitura pesada
|
|
7
|
+
|
|
8
|
+
**Verified against:** Neon docs lidas em 2026-10-30 (regions, plans, backup-restore, branch-expiration, s3-compatibility, ai-gateway, neon-mcp-server, functions) + CLI `neon` 8.14.0. Nenhum bloco deste standard foi executado contra um projeto. Last-verified: 2026-10-30.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Antes de escrever código ou contratar serviço, confira se o Neon do projeto já resolve. Preço, plano e status mudam rápido: em dúvida, `mcp__neon__search_docs`.
|
|
13
|
+
|
|
14
|
+
## Região decide o catálogo
|
|
15
|
+
|
|
16
|
+
A região é fixa na criação do projeto; mudar é criar outro projeto e migrar. Postgres e Auth existem em todas. O resto não:
|
|
17
|
+
|
|
18
|
+
| Região | Storage, Functions, AI Gateway, Realtime |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `aws-us-east-1`, `aws-us-east-2`, `aws-eu-central-1`, `aws-ap-southeast-1` | Sim |
|
|
21
|
+
| `aws-sa-east-1`, `aws-us-west-2`, `aws-eu-west-2`, `aws-ap-southeast-2` | Não |
|
|
22
|
+
|
|
23
|
+
`neon projects get <id>` mostra o `region_id`. Projeto em São Paulo fica sem esses quatro produtos: pese isso antes de escolher a região pela latência.
|
|
24
|
+
|
|
25
|
+
## Necessidade → o que usar
|
|
26
|
+
|
|
27
|
+
| Necessidade | No Neon | Pegadinha |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| Testar migration ou dado real sem tocar produção | Branch com `--expires-at` (máx. 30 dias), `neon branches reset --parent` para recomeçar | Reset sobrescreve o branch, sem backup. `--schema-only` está em beta e não tem pai, logo não tem reset |
|
|
30
|
+
| Ensaiar migration | MCP `prepare_database_migration` → `complete_database_migration` | Aplica SQL em branch temporário: confere o SQL gerado pelo `dotnet ef migrations script --idempotent`, não substitui o `dotnet ef` |
|
|
31
|
+
| Ver o que mudou no schema | `neon branches schema-diff`, `neon diff`, MCP `compare_database_schema` | Compara, não aplica |
|
|
32
|
+
| Rede de segurança de migration com `degrau:` | Snapshot manual antes de aplicar (MCP `create_snapshot`, `neon snapshots`); volta por PITR dentro da janela | Janela default curta (6 h no Free, 1 dia nos pagos, ajustável). Só root branch. Branch restaurado de snapshot perde PITR |
|
|
33
|
+
| Arquivo do usuário, mídia gerada | Object Storage, S3-compatible, GA | `$0,023/GB-mês`. Lifecycle e versioning são guardados, não aplicados. Sem notificação de bucket. Cliente .NET por S3 path-style não testado aqui |
|
|
34
|
+
| Uma credencial para vários modelos de LLM | AI Gateway, OpenAI-compatible, GA | Plano pago e crédito pré-pago. Foge do padrão de provider direto do `model-registry`: exige ADR |
|
|
35
|
+
| Busca por termo em escala | Lakebase Search (`infrastructure-neon-pgvector#lakebase-search-neon-only-upgrade`) | Só existe no Neon |
|
|
36
|
+
| Consulta lenta | MCP `list_slow_queries`, `explain_sql_statement`, `prepare_query_tuning` → `complete_query_tuning`; `pg_stat_statements` | Histórico some quando o compute suspende. `query tuning` testa índice em branch |
|
|
37
|
+
| Relatório pesado sem pesar no primário | Read replica, com connection string própria | Eventualmente consistente, mesma região, cobra compute |
|
|
38
|
+
| Fechar o banco ao IP da VPS | IP Allow, só plano Scale | Incompatível com Neon Auth e Data API: quem usa Auth não liga |
|
|
39
|
+
| Impedir que apaguem produção | Protected branch (Launch: 2, Scale: 5) | Filhos ganham senha nova e scripts de CI quebram |
|
|
40
|
+
| Alerta e métrica fora do console | Export OTel/Datadog, só Scale, em beta | Lacuna enquanto o compute está suspenso |
|
|
41
|
+
|
|
42
|
+
## O que não adotar nesta stack
|
|
43
|
+
|
|
44
|
+
- **Neon Functions para job .NET:** só JS/TS, e a doc diz "They aren't a job queue". Serve para schedule em UTC que faz POST num endpoint idempotente.
|
|
45
|
+
- **Data API:** o EF Core já é o acesso. Sem autorização própria além de `GRANT`/RLS.
|
|
46
|
+
- **Realtime:** beta, sem RLS, SDK só TypeScript.
|
|
47
|
+
- **Neon Local:** é proxy para a nuvem, não Postgres local. Dev segue no Docker Compose (`infrastructure-docker-local-compose-isolation`).
|
|
48
|
+
- **Private Networking:** exige o cliente na AWS, na mesma região. A VPS não está.
|
|
49
|
+
- **Logical replication sem necessidade real:** reinicia computes e `wal_level` é irreversível.
|
|
50
|
+
|
|
51
|
+
## MCP em produção
|
|
52
|
+
|
|
53
|
+
A doc do Neon é explícita: "Never connect MCP agents to production databases". O agente trabalha em branch. O servidor expõe tools destrutivas, e toda escrita em produção passa por um humano, com snapshot antes quando houver `degrau:`.
|
|
54
|
+
|
|
55
|
+
## CLI
|
|
56
|
+
|
|
57
|
+
O pacote agora é `neon` (`npm i -g neon`, Node 20.19+). `neonctl` segue funcionando como o mesmo binário. Login é `neon login`. Comandos úteis no dia a dia: `neon branches`, `neon diff`, `neon snapshots`, `neon psql`, `neon logs`, `neon buckets`, `neon connection-string`, `neon checkout`, `neon env pull`. `neon.ts` (`neon config init`) declara branches em TypeScript: útil no front, opcional em projeto .NET, que lê `user-secrets`.
|
|
58
|
+
|
|
59
|
+
## Fora do catálogo
|
|
60
|
+
|
|
61
|
+
`pg_search` foi removido, `pgrag` e `plv8` estão deprecados, `pgmq` e Apache AGE não estão na lista de extensões. Lista viva: `docs/extensions/pg-extensions`.
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> **Scope:** neon
|
|
4
4
|
> **Layer:** 0 (resumo injetado pela rule `neon-standards` em `**/neon/**`, `**/database/**` e migrations `.sql`; texto completo sob demanda via `morph-spec standards --find`)
|
|
5
|
-
> **Keywords:** neon, serverless postgres, branching, scale-to-zero, connection string, neonctl
|
|
5
|
+
> **Keywords:** neon, serverless postgres, branching, scale-to-zero, connection string, neon cli, neonctl
|
|
6
6
|
> **Load When:** neon stack detected
|
|
7
7
|
|
|
8
|
-
**Verified against:** .NET 10 (SDK 10.0.400-preview) + Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + neonctl. The DbContext registration, design-time factory and cold-start blocks compile with `TreatWarningsAsErrors`; that Npgsql 10 rejects `postgresql://` URIs and marks `Trust Server Certificate` as "no longer needed and does nothing" was measured on 10.0.3; the suspend-timeout API and `--cu` ranges were checked against the Neon docs (context7).
|
|
8
|
+
**Verified against:** .NET 10 (SDK 10.0.400-preview) + Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 + neonctl. The DbContext registration, design-time factory and cold-start blocks compile with `TreatWarningsAsErrors`; that Npgsql 10 rejects `postgresql://` URIs and marks `Trust Server Certificate` as "no longer needed and does nothing" was measured on 10.0.3; the suspend-timeout API and `--cu` ranges were checked against the Neon docs (context7). `neon` CLI commands are documentary; the top-level commands were read from `neon --help` 8.14.0. Last-verified: 2026-10-30.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -18,19 +18,20 @@ Stack: .NET + Neon Serverless Postgres
|
|
|
18
18
|
- NEVER hardcode connection strings or commit them in `appsettings*.json` -- user-secrets in development, environment variables in Coolify
|
|
19
19
|
- ALWAYS enable SSL (`SSL Mode=Require`) for all connections
|
|
20
20
|
- ALWAYS configure retry policies for cold-start resilience
|
|
21
|
-
- NEVER leave branches without expiration -- set TTL for dev/CI branches
|
|
21
|
+
- NEVER leave branches without expiration -- set TTL for dev/CI branches (`--expires-at`, 30 days at most)
|
|
22
|
+
- ALWAYS take a snapshot before applying a migration with `degrau:` to production (`infrastructure-neon-platform`)
|
|
22
23
|
|
|
23
24
|
## Neon CLI Setup
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
|
-
# Install
|
|
27
|
-
npm install -g
|
|
27
|
+
# Install neon globally
|
|
28
|
+
npm install -g neon
|
|
28
29
|
|
|
29
30
|
# Authenticate (opens browser)
|
|
30
|
-
|
|
31
|
+
neon login
|
|
31
32
|
|
|
32
33
|
# Verify connection
|
|
33
|
-
|
|
34
|
+
neon projects list
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
## Connection Strings
|
|
@@ -130,24 +131,24 @@ Neon branches are copy-on-write clones of your database -- instant, zero-cost un
|
|
|
130
131
|
|
|
131
132
|
```bash
|
|
132
133
|
# List branches
|
|
133
|
-
|
|
134
|
+
neon branches list --project-id <project-id>
|
|
134
135
|
|
|
135
136
|
# Create feature branch from main
|
|
136
|
-
|
|
137
|
+
neon branches create --project-id <project-id> \
|
|
137
138
|
--name feature/my-feature \
|
|
138
139
|
--parent main
|
|
139
140
|
|
|
140
141
|
# Create branch with auto-expiration (CI/CD)
|
|
141
|
-
|
|
142
|
+
neon branches create --project-id <project-id> \
|
|
142
143
|
--name ci-test-run-42 \
|
|
143
144
|
--parent main \
|
|
144
145
|
--expires-at "2027-01-01T00:00:00Z"
|
|
145
146
|
|
|
146
147
|
# Reset branch to parent state
|
|
147
|
-
|
|
148
|
+
neon branches reset feature/my-feature --parent --project-id <project-id>
|
|
148
149
|
|
|
149
150
|
# Delete branch
|
|
150
|
-
|
|
151
|
+
neon branches delete feature/my-feature --project-id <project-id>
|
|
151
152
|
```
|
|
152
153
|
|
|
153
154
|
### Branch Strategy
|
|
@@ -189,7 +190,7 @@ Set it when the branch (and its compute) is created; `main` already exists, so i
|
|
|
189
190
|
|
|
190
191
|
```bash
|
|
191
192
|
# New branch with a 10-minute suspend timeout and an autoscaling range
|
|
192
|
-
|
|
193
|
+
neon branches create --project-id <project-id> \
|
|
193
194
|
--name feature/my-feature \
|
|
194
195
|
--parent main \
|
|
195
196
|
--suspend-timeout 600 \
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Keywords:** mcp, model-context-protocol, mcp-tools, context7, playwright, mcp-client
|
|
6
6
|
> **Load When:** an MCP tool is used in any morph-spec phase
|
|
7
7
|
|
|
8
|
-
**Verified against:** Claude Code 2.1.282 MCP tooling — context7, Playwright (as plugin and as a project MCP server); GitHub via `gh` CLI; Neon via its MCP or `
|
|
8
|
+
**Verified against:** Claude Code 2.1.282 MCP tooling — context7, Playwright (as plugin and as a project MCP server); GitHub via `gh` CLI; Neon via its MCP or `neon`; Microsoft Learn MCP (`https://learn.microsoft.com/api/mcp`, tools listed by `tools/list` on 2026-10-17). Last-verified: 2026-10-30.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -19,7 +19,7 @@ In a morph persona the grant follows the same rule: the installer emits one `mcp
|
|
|
19
19
|
|
|
20
20
|
## MCP Availability Detection
|
|
21
21
|
|
|
22
|
-
Before using any MCP tool, check if it's available in the current session. There is no "list MCPs" command: attempt the most common operation, and Claude Code reports a tool that doesn't exist. GitHub and Neon are not checked this way — o morph-spec NÃO usa MCP do GitHub nem do Neon: use o `gh` CLI (`gh repo view --json name,defaultBranchRef,languages`) e o `
|
|
22
|
+
Before using any MCP tool, check if it's available in the current session. There is no "list MCPs" command: attempt the most common operation, and Claude Code reports a tool that doesn't exist. GitHub and Neon are not checked this way — o morph-spec NÃO usa MCP do GitHub nem do Neon: use o `gh` CLI (`gh repo view --json name,defaultBranchRef,languages`) e o `neon` + `psql` (`neon connection-string` dá a URL do banco) via Bash.
|
|
23
23
|
|
|
24
24
|
```javascript
|
|
25
25
|
await mcp__context7__resolve-library-id({ libraryName: "react" });
|
|
@@ -136,7 +136,7 @@ Fallback: manual image creation, stock photos, or placeholder divs.
|
|
|
136
136
|
|
|
137
137
|
| Tool | Use Case | Example |
|
|
138
138
|
|------|----------|---------|
|
|
139
|
-
| **Neon CLI (`
|
|
139
|
+
| **Neon CLI (`neon`) / psql** | Database schema, tables, relationships, RLS | `psql <DB_URL> -c "SELECT ..."` |
|
|
140
140
|
| **Context7** | Library docs for architecture decisions | `mcp__context7__query-docs()` |
|
|
141
141
|
| **Microsoft Learn** (stack .NET) | Official docs for a .NET/EF Core/MAF decision (limits, what is new, migration) | `mcp__microsoft-learn__microsoft_docs_search({ query })` |
|
|
142
142
|
| **GitHub CLI (`gh`)** | Check existing code patterns, PRs | `gh search code` (Bash) |
|
|
@@ -145,25 +145,25 @@ Fallback: manual image creation, stock photos, or placeholder divs.
|
|
|
145
145
|
# === SCHEMA ANALYSIS (Critical for contracts.cs) ===
|
|
146
146
|
|
|
147
147
|
# 0. Get Neon DB connection string
|
|
148
|
-
|
|
148
|
+
neon connection-string
|
|
149
149
|
# → e.g.: postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/neondb?sslmode=require
|
|
150
150
|
|
|
151
151
|
# 1. List all tables
|
|
152
|
-
pg_dump $(
|
|
152
|
+
pg_dump $(neon connection-string) --schema-only --schema public 2>/dev/null | grep "CREATE TABLE"
|
|
153
153
|
|
|
154
154
|
# 2. Get schema for each relevant table
|
|
155
|
-
psql $(
|
|
155
|
+
psql $(neon connection-string) -c "SELECT column_name, data_type, is_nullable, column_default \
|
|
156
156
|
FROM information_schema.columns WHERE table_name = 'leads' ORDER BY ordinal_position"
|
|
157
157
|
|
|
158
158
|
# 3. Get foreign key relationships
|
|
159
|
-
psql $(
|
|
159
|
+
psql $(neon connection-string) -c "SELECT tc.table_name, kcu.column_name, ccu.table_name AS foreign_table, \
|
|
160
160
|
ccu.column_name AS foreign_column FROM information_schema.table_constraints tc \
|
|
161
161
|
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name \
|
|
162
162
|
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name \
|
|
163
163
|
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = 'leads'"
|
|
164
164
|
|
|
165
165
|
# 4. Check RLS policies (security-critical)
|
|
166
|
-
psql $(
|
|
166
|
+
psql $(neon connection-string) -c "SELECT tablename, policyname, cmd, qual FROM pg_policies WHERE tablename = 'leads'"
|
|
167
167
|
```
|
|
168
168
|
|
|
169
169
|
**Fallback (Neon database not accessible):**
|
|
@@ -211,7 +211,7 @@ gh issue create --title "F1: Create Entity Lead" \
|
|
|
211
211
|
|
|
212
212
|
| Tool | Use Case | Example |
|
|
213
213
|
|------|----------|---------|
|
|
214
|
-
| **Neon CLI (`
|
|
214
|
+
| **Neon CLI (`neon`) / psql** | Run migrations, create RLS policies | `dotnet ef database update` |
|
|
215
215
|
| **GitHub CLI (`gh`)** | Create PR, push branches | `gh pr create` (Bash) |
|
|
216
216
|
| **Context7** | Look up API usage during coding | `mcp__context7__query-docs()` |
|
|
217
217
|
| **Microsoft Learn** (stack .NET) | Current .NET 10 / EF Core / ASP.NET Core / MAF API: search, then fetch the page when the excerpt is cut | `mcp__microsoft-learn__microsoft_docs_search({ query })` → `microsoft_docs_fetch({ url })`; `microsoft_code_sample_search` for snippets |
|
|
@@ -230,10 +230,10 @@ dotnet ef migrations add AddStatusToLeads
|
|
|
230
230
|
dotnet ef database update
|
|
231
231
|
|
|
232
232
|
# Verify schema after migration
|
|
233
|
-
psql $(
|
|
233
|
+
psql $(neon connection-string) -c "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'leads'"
|
|
234
234
|
|
|
235
235
|
# Verify RLS policies
|
|
236
|
-
psql $(
|
|
236
|
+
psql $(neon connection-string) -c "SELECT policyname, cmd, qual FROM pg_policies WHERE tablename = 'leads'"
|
|
237
237
|
```
|
|
238
238
|
|
|
239
239
|
Look up the API while coding, then smoke test the deployed feature: the snapshot shows whether the page renders, the console messages catch JavaScript errors and failed API calls, and the screenshot goes to the slice report (`3-implement/reports/F{n}.md`). Vercel covers deployment status, logs and environment variables.
|
|
@@ -270,7 +270,7 @@ PRs/issues: sempre `gh` CLI (Bash). Deployments fallback: `vercel` CLI.
|
|
|
270
270
|
|
|
271
271
|
| Need | MCP Tool | Native Alternative |
|
|
272
272
|
|------|----------|--------------------|
|
|
273
|
-
| Database schema | Neon CLI (`
|
|
273
|
+
| Database schema | Neon CLI (`neon`) / psql | Grep queries + Read types |
|
|
274
274
|
| Repo metadata / issues / PRs | Bash `gh` CLI | WebFetch `api.github.com` (read-only) |
|
|
275
275
|
| Library docs | Context7 | WebSearch + WebFetch |
|
|
276
276
|
| Live page preview | Playwright MCP | WebFetch URL |
|
|
@@ -335,26 +335,26 @@ await mcp__playwright__browser_file_upload({ ref: "s1e20", paths: ["/path/to/fil
|
|
|
335
335
|
|
|
336
336
|
---
|
|
337
337
|
|
|
338
|
-
### Neon: Full Schema Discovery (via
|
|
338
|
+
### Neon: Full Schema Discovery (via neon + psql)
|
|
339
339
|
|
|
340
|
-
> **Primary:** Neon CLI (`
|
|
341
|
-
> **Connection:** Always use `
|
|
340
|
+
> **Primary:** Neon CLI (`neon`) + psql connecting to Neon database.
|
|
341
|
+
> **Connection:** Always use `neon connection-string` to get the DB URL.
|
|
342
342
|
|
|
343
343
|
```bash
|
|
344
344
|
# Complete workflow for Phase 3 schema analysis
|
|
345
345
|
|
|
346
346
|
# 1. Get Neon DB URL
|
|
347
|
-
|
|
347
|
+
neon connection-string # → pooled connection string
|
|
348
348
|
|
|
349
349
|
# 2. List all tables
|
|
350
|
-
pg_dump $(
|
|
350
|
+
pg_dump $(neon connection-string) --schema-only --schema public 2>/dev/null | grep "CREATE TABLE"
|
|
351
351
|
|
|
352
352
|
# 3. For each relevant table:
|
|
353
|
-
psql $(
|
|
353
|
+
psql $(neon connection-string) -c "SELECT column_name, data_type, is_nullable, column_default \
|
|
354
354
|
FROM information_schema.columns WHERE table_name = 'TABLE_NAME' ORDER BY ordinal_position"
|
|
355
355
|
|
|
356
356
|
# 4. Get relationships
|
|
357
|
-
psql $(
|
|
357
|
+
psql $(neon connection-string) -c "SELECT tc.table_name, kcu.column_name, ccu.table_name AS foreign_table \
|
|
358
358
|
FROM information_schema.table_constraints tc \
|
|
359
359
|
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name \
|
|
360
360
|
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name \
|
|
@@ -397,7 +397,7 @@ gh api repos/{owner}/{repo}/branches --jq '.[].name'
|
|
|
397
397
|
## Security Considerations
|
|
398
398
|
|
|
399
399
|
- **Never pass secrets** in MCP tool parameters (API keys, tokens, passwords)
|
|
400
|
-
- **Neon** uses
|
|
400
|
+
- **Neon** uses neon/psql — use database branches for development, not production
|
|
401
401
|
- **GitHub via `gh` CLI** herda as permissões do login do `gh auth login` (keyring) — sem PAT em env var, sem Docker
|
|
402
402
|
- **Always validate tool responses** before using in contracts or code generation
|
|
403
403
|
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
<O que dói hoje, para quem, e como se percebe. Duas a cinco frases.>
|
|
9
9
|
|
|
10
|
+
Resultado buscado: <o estado depois: o que muda, para quem, como se percebe.>
|
|
11
|
+
|
|
10
12
|
## Fora de escopo
|
|
11
13
|
|
|
12
14
|
- <o que esta feature deliberadamente não faz>
|
|
@@ -69,6 +71,7 @@ nenhum
|
|
|
69
71
|
|
|
70
72
|
## Contrato de implementação
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
- **Instrumentos:** <o que falta para provar sozinho: ferramenta, acesso, dado, ambiente; "nenhuma lacuna" vale.>
|
|
75
|
+
- <Assinaturas, records, enums e rotas que a implementação honra; runtime do e2e; baseline de teste como comando.>
|
|
73
76
|
|
|
74
77
|
## Errata
|
package/package.json
CHANGED
|
@@ -30,7 +30,7 @@ export function autoApprovalRefusal({ cwd, featureName, feature, gate, approvedB
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
if (gate === 'plan') {
|
|
33
|
-
const block = planAutoApprovalBlock(cwd, featureName);
|
|
33
|
+
const block = planAutoApprovalBlock(cwd, featureName, feature);
|
|
34
34
|
if (block) {
|
|
35
35
|
return {
|
|
36
36
|
title: `O gate "plan" não pode ser auto-aprovado — ${block}.`,
|
|
@@ -132,7 +132,7 @@ export async function createCommand(featureName, options = {}) {
|
|
|
132
132
|
const alreadyExists = existsSync(join(process.cwd(), '.morph', 'features', featureName, 'feature.json'));
|
|
133
133
|
if (!alreadyExists && (description === null || String(description).trim() === '')) {
|
|
134
134
|
console.error(chalk.red(`\n❌ A feature needs a description at birth: "${featureName}"`));
|
|
135
|
-
console.error(chalk.gray(' Pass --description "<what/why>" (or --request
|
|
135
|
+
console.error(chalk.gray(' Pass --description "<what/why>" (or --request '<pedido>', which seeds it).'));
|
|
136
136
|
console.error(chalk.gray(' This keeps features identifiable — no more anonymous scaffolds.\n'));
|
|
137
137
|
process.exit(1);
|
|
138
138
|
}
|
|
@@ -59,9 +59,9 @@ export function collectReviewSignal(cwd, featureName, feature, planChecks = read
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/** Why an automatic signature may not close the plan gate: the rule that makes gate-check pause it. */
|
|
62
|
-
export function planAutoApprovalBlock(cwd, featureName) {
|
|
62
|
+
export function planAutoApprovalBlock(cwd, featureName, feature) {
|
|
63
63
|
const planChecks = readPlanChecks(cwd, featureName);
|
|
64
|
-
const workType =
|
|
64
|
+
const workType = feature?.workType || 'feature';
|
|
65
65
|
const reason = planChecksPause(planChecks.status)
|
|
66
66
|
?? planCritiquePause(readPlanCritique(cwd, featureName, { workType, planChecks }))
|
|
67
67
|
?? rungPause({ gate: 'plan', rungs: pausingRungs(planChecks.checks) })?.reason;
|
|
@@ -54,7 +54,7 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
54
54
|
if (depNames.includes('@neondatabase/serverless') || depNames.includes('postgres') || depNames.includes('pg')) {
|
|
55
55
|
structure.integrations.push('neon');
|
|
56
56
|
}
|
|
57
|
-
if (depNames.includes('@neondatabase/auth')) {
|
|
57
|
+
if (depNames.includes('@neon/auth') || depNames.includes('@neondatabase/auth')) {
|
|
58
58
|
structure.integrations.push('neon-auth');
|
|
59
59
|
}
|
|
60
60
|
if (depNames.includes('stripe')) {
|
|
@@ -39,7 +39,7 @@ metadata:
|
|
|
39
39
|
|
|
40
40
|
| Command | Skill invoked |
|
|
41
41
|
|---------|---------------|
|
|
42
|
-
| \`/morph-plan {feature}\` | \`Skill(morph-plan)\` —
|
|
42
|
+
| \`/morph-plan {feature}\` | \`Skill(morph-plan)\` — tese → plan.md → plan-check → gate do plano |
|
|
43
43
|
| \`/morph-apply {feature}\` | \`Skill(morph-apply)\` — fatias do plano → verify → review → Gate 3 |
|
|
44
44
|
| \`/morph-status [feature]\` | inspect state.json |
|
|
45
45
|
| \`/morph-archive {feature}\` | move completed feature to .morph/archive/ |
|
package/src/lib/plan/checks.js
CHANGED
|
@@ -123,9 +123,16 @@ export function validatePlan(plan, { workType = null, riskRules = true, text = n
|
|
|
123
123
|
errors.push(...statesErrors(plan, at));
|
|
124
124
|
warnings.push(...defaultsWarnings(plan, at));
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
const isSlices = (s) => /^fatias$/i.test(s.name.trim());
|
|
127
|
+
if (plan.sections.some((s) => !s.key && isSlices(s))) {
|
|
127
128
|
warnings.push('## Fatias não é lida desde a 10.0: o plano inteiro se implementa numa sessão, e os cenários definem o pronto');
|
|
128
129
|
}
|
|
130
|
+
const known = new Set(Object.values(SECTIONS).map(foldRisk));
|
|
131
|
+
for (const s of plan.sections.filter((section) => !section.key && !isSlices(section))) {
|
|
132
|
+
warnings.push(known.has(foldRisk(s.name))
|
|
133
|
+
? `## ${s.name} repetida${at(s.line)} — só a primeira é lida, o conteúdo desta se perde`
|
|
134
|
+
: `## ${s.name} não é seção do plano${at(s.line)} — o plan-check não a lê`);
|
|
135
|
+
}
|
|
129
136
|
|
|
130
137
|
for (const r of plan.reuse) {
|
|
131
138
|
if (!REUSE_VERDICTS.includes(r.verdict)) {
|