@polymorphism-tech/morph-spec 4.16.0 → 4.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/morph-spec.js +3 -0
- package/bin/task-manager.js +999 -962
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents/ai-agents/vector-search-expert.md +247 -233
- package/framework/agents/backend/ms-agent-expert.md +1 -1
- package/framework/agents/backend/neon-expert.md +397 -0
- package/framework/agents/frontend/gsap-animator.md +131 -0
- package/framework/agents.json +1175 -1085
- package/framework/commands/morph-preflight.md +200 -192
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +1 -1
- package/framework/rules/gsap-standards.md +13 -0
- package/framework/rules/neon-standards.md +25 -0
- package/framework/skills/level-0-meta/mcp-registry.json +1 -1
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +472 -472
- package/framework/skills/level-0-meta/morph-init/SKILL.md +11 -11
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +7 -7
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +24 -24
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +12 -12
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +9 -9
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +143 -143
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +184 -107
- package/framework/standards/ai-agents/production.md +1 -0
- package/framework/standards/ai-agents/setup.md +3 -2
- package/framework/standards/ai-agents/workflows.md +1 -0
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +4 -4
- package/framework/standards/backend/database/postgresql/database.md +58 -78
- package/framework/standards/backend/dotnet/scalar-ui.md +172 -172
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -274
- package/framework/standards/backend/integrations/asaas/asaas-api.md +1 -1
- package/framework/standards/backend/integrations/clerk/clerk-auth.md +23 -21
- package/framework/standards/frontend/design-system/animations.md +9 -0
- package/framework/standards/frontend/gsap/gsap-core.md +463 -0
- package/framework/standards/frontend/gsap/gsap-plugins.md +294 -0
- package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +318 -0
- package/framework/standards/frontend/nextjs/motion-patterns.md +1 -0
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +47 -37
- package/framework/standards/infrastructure/docker/easypanel-deploy.md +4 -8
- package/framework/standards/infrastructure/neon/neon-pgrag.md +239 -0
- package/framework/standards/infrastructure/neon/neon-pgvector.md +236 -0
- package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +310 -0
- package/framework/standards/infrastructure/neon/neon-setup.md +186 -0
- package/framework/standards/integration/mcp/mcp-tools.md +32 -34
- package/framework/templates/README.md +18 -18
- package/framework/templates/REGISTRY.json +49 -96
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/backend/repository.cs +148 -148
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/database/vector-entity.cs +93 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +1 -1
- package/framework/templates/code/sql/{supabase-migration.sql → postgresql-migration.sql} +8 -17
- package/framework/templates/code/sql/{supabase-migration.template.sql → postgresql-migration.template.sql} +10 -13
- package/framework/templates/code/sql/{rls-policy.sql → postgresql-rls-policy.sql} +8 -8
- package/framework/templates/code/typescript/contracts.ts +167 -168
- package/framework/templates/context/CONTEXT.md +3 -3
- package/framework/templates/docs/proposal.md +6 -6
- package/framework/templates/docs/ui-components.md +1 -1
- package/framework/templates/feature/decisions.md +121 -121
- package/framework/templates/feature/recap.md +145 -145
- package/framework/templates/feature/tasks.md +199 -199
- package/framework/templates/frontend/nextjs/gsap-scroll-section.tsx.hbs +44 -0
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -67
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/docker/easypanel.template.json +46 -54
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/integrations/semantic-kernel-neon.cs +125 -0
- package/framework/workflows/README.md +1 -1
- package/framework/workflows/docs/full-morph.md +69 -69
- package/package.json +1 -1
- package/src/commands/agents/dispatch-agents.js +19 -15
- package/src/commands/state/advance-phase.js +5 -0
- package/src/commands/tasks/task.js +3 -0
- package/src/core/state/state-manager.js +45 -1
- package/src/core/templates/template-registry.js +1 -66
- package/src/lib/detectors/index.js +2 -2
- package/src/lib/generators/architecture-scanner.js +640 -640
- package/src/lib/installers/mcp-installer.js +1 -1
- package/src/lib/tasks/task-classifier.js +15 -1
- package/src/lib/tasks/test-runner.js +5 -1
- package/src/lib/validators/content/content-validator.js +9 -10
- package/src/lib/validators/validation-runner.js +7 -5
- package/framework/agents/backend/supabase-expert.md +0 -256
- package/framework/rules/supabase-standards.md +0 -29
- package/framework/standards/infrastructure/supabase/mcp-setup.md +0 -142
- package/framework/standards/infrastructure/supabase/supabase-auth.md +0 -176
- package/framework/standards/infrastructure/supabase/supabase-custom-jwt.md +0 -206
- package/framework/standards/infrastructure/supabase/supabase-pgvector.md +0 -169
- package/framework/standards/infrastructure/supabase/supabase-rls.md +0 -184
- package/framework/standards/infrastructure/supabase/supabase-storage.md +0 -153
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Neon Semantic Kernel Vector Store Standard
|
|
2
|
+
|
|
3
|
+
> **Scope:** neon, dotnet-vsa
|
|
4
|
+
> **Layer:** 2 (on keyword)
|
|
5
|
+
> **Keywords:** semantic kernel, vector store, PostgresVectorStore, VectorStoreTextSearch, MAF
|
|
6
|
+
> **Load When:** semantic kernel postgres vector keywords detected
|
|
7
|
+
|
|
8
|
+
Stack: .NET VSA + Neon Serverless Postgres + Semantic Kernel
|
|
9
|
+
|
|
10
|
+
## Core Rules
|
|
11
|
+
|
|
12
|
+
- ALWAYS call `UseVector()` on `NpgsqlDataSourceBuilder` -- without it the vector store fails silently
|
|
13
|
+
- ALWAYS use `AddPostgresVectorStore()` for DI registration -- handles datasource lifecycle
|
|
14
|
+
- NEVER mix pgvector-dotnet `Vector` type with SK `ReadOnlyMemory<float>` -- SK uses its own mapping
|
|
15
|
+
- ALWAYS annotate data model properties with `[VectorStoreKey]`, `[VectorStoreData]`, `[VectorStoreVector]`
|
|
16
|
+
- Use `PostgresCollection<TKey, TRecord>` for typed collection access
|
|
17
|
+
|
|
18
|
+
## NuGet Packages
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
dotnet add package Microsoft.SemanticKernel.Connectors.PgVector --prerelease
|
|
22
|
+
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI
|
|
23
|
+
dotnet add package Npgsql
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## PostgresVectorStore Initialization
|
|
27
|
+
|
|
28
|
+
### Via Dependency Injection (Recommended)
|
|
29
|
+
|
|
30
|
+
```csharp
|
|
31
|
+
using Microsoft.Extensions.DependencyInjection;
|
|
32
|
+
using Microsoft.SemanticKernel;
|
|
33
|
+
using Npgsql;
|
|
34
|
+
|
|
35
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
36
|
+
|
|
37
|
+
// Option 1: Connection string (UseVector applied automatically)
|
|
38
|
+
builder.Services.AddPostgresVectorStore(
|
|
39
|
+
builder.Configuration.GetConnectionString("DefaultConnection")!);
|
|
40
|
+
|
|
41
|
+
// Option 2: Manual NpgsqlDataSource (full control)
|
|
42
|
+
builder.Services.AddSingleton<NpgsqlDataSource>(sp =>
|
|
43
|
+
{
|
|
44
|
+
var dataSourceBuilder = new NpgsqlDataSourceBuilder(
|
|
45
|
+
builder.Configuration.GetConnectionString("DefaultConnection"));
|
|
46
|
+
dataSourceBuilder.UseVector();
|
|
47
|
+
return dataSourceBuilder.Build();
|
|
48
|
+
});
|
|
49
|
+
builder.Services.AddPostgresVectorStore();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Direct Construction
|
|
53
|
+
|
|
54
|
+
```csharp
|
|
55
|
+
using Microsoft.SemanticKernel.Connectors.PgVector;
|
|
56
|
+
using Npgsql;
|
|
57
|
+
|
|
58
|
+
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
|
|
59
|
+
dataSourceBuilder.UseVector();
|
|
60
|
+
var dataSource = dataSourceBuilder.Build();
|
|
61
|
+
|
|
62
|
+
var vectorStore = new PostgresVectorStore(dataSource, ownsDataSource: true);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Data Model Definition
|
|
66
|
+
|
|
67
|
+
Annotate your record type with SK vector store attributes:
|
|
68
|
+
|
|
69
|
+
```csharp
|
|
70
|
+
using System;
|
|
71
|
+
using Microsoft.Extensions.VectorData;
|
|
72
|
+
|
|
73
|
+
public class KnowledgeEntry
|
|
74
|
+
{
|
|
75
|
+
[VectorStoreKey(StorageName = "id")]
|
|
76
|
+
public Guid Id { get; set; }
|
|
77
|
+
|
|
78
|
+
[VectorStoreData(StorageName = "title")]
|
|
79
|
+
public string Title { get; set; } = string.Empty;
|
|
80
|
+
|
|
81
|
+
[VectorStoreData(StorageName = "content")]
|
|
82
|
+
public string Content { get; set; } = string.Empty;
|
|
83
|
+
|
|
84
|
+
[VectorStoreData(StorageName = "source")]
|
|
85
|
+
public string? Source { get; set; }
|
|
86
|
+
|
|
87
|
+
[VectorStoreData(StorageName = "created_at")]
|
|
88
|
+
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
|
89
|
+
|
|
90
|
+
[VectorStoreVector(
|
|
91
|
+
Dimensions: 1536,
|
|
92
|
+
DistanceFunction = DistanceFunction.CosineDistance,
|
|
93
|
+
IndexKind = IndexKind.Hnsw,
|
|
94
|
+
StorageName = "embedding")]
|
|
95
|
+
public ReadOnlyMemory<float>? Embedding { get; set; }
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This maps to the following Postgres table (auto-created by `EnsureCollectionExistsAsync`):
|
|
100
|
+
|
|
101
|
+
```sql
|
|
102
|
+
CREATE TABLE IF NOT EXISTS public."knowledge_entries" (
|
|
103
|
+
"id" UUID PRIMARY KEY NOT NULL,
|
|
104
|
+
"title" TEXT,
|
|
105
|
+
"content" TEXT,
|
|
106
|
+
"source" TEXT,
|
|
107
|
+
"created_at" TIMESTAMPTZ,
|
|
108
|
+
"embedding" VECTOR(1536)
|
|
109
|
+
);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Collection Operations
|
|
113
|
+
|
|
114
|
+
```csharp
|
|
115
|
+
using Microsoft.SemanticKernel.Connectors.PgVector;
|
|
116
|
+
|
|
117
|
+
// Get a typed collection
|
|
118
|
+
var collection = vectorStore.GetCollection<Guid, KnowledgeEntry>("knowledge_entries");
|
|
119
|
+
|
|
120
|
+
// Create table if it doesn't exist (includes HNSW index)
|
|
121
|
+
await collection.EnsureCollectionExistsAsync();
|
|
122
|
+
|
|
123
|
+
// Upsert a record
|
|
124
|
+
var entry = new KnowledgeEntry
|
|
125
|
+
{
|
|
126
|
+
Id = Guid.NewGuid(),
|
|
127
|
+
Title = "Neon Overview",
|
|
128
|
+
Content = "Neon is a serverless Postgres platform...",
|
|
129
|
+
Source = "docs",
|
|
130
|
+
Embedding = new ReadOnlyMemory<float>(embeddingVector)
|
|
131
|
+
};
|
|
132
|
+
await collection.UpsertAsync(entry);
|
|
133
|
+
|
|
134
|
+
// Get by key
|
|
135
|
+
var retrieved = await collection.GetAsync(entry.Id);
|
|
136
|
+
|
|
137
|
+
// Delete
|
|
138
|
+
await collection.DeleteAsync(entry.Id);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Embedding Generation with OpenAI
|
|
142
|
+
|
|
143
|
+
```csharp
|
|
144
|
+
using Microsoft.SemanticKernel;
|
|
145
|
+
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
|
146
|
+
using Microsoft.SemanticKernel.Embeddings;
|
|
147
|
+
|
|
148
|
+
// Register embedding service
|
|
149
|
+
builder.Services.AddOpenAITextEmbeddingGeneration(
|
|
150
|
+
modelId: "text-embedding-3-small",
|
|
151
|
+
apiKey: builder.Configuration["OpenAI:ApiKey"]!);
|
|
152
|
+
|
|
153
|
+
// Generate embeddings
|
|
154
|
+
public class EmbeddingService(ITextEmbeddingGenerationService embeddingService)
|
|
155
|
+
{
|
|
156
|
+
public async Task<ReadOnlyMemory<float>> GenerateAsync(string text)
|
|
157
|
+
{
|
|
158
|
+
var embeddings = await embeddingService.GenerateEmbeddingsAsync([text]);
|
|
159
|
+
return embeddings[0];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public async Task<IList<ReadOnlyMemory<float>>> GenerateBatchAsync(IList<string> texts)
|
|
163
|
+
{
|
|
164
|
+
return await embeddingService.GenerateEmbeddingsAsync(texts);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## VectorStoreTextSearch Usage
|
|
170
|
+
|
|
171
|
+
`VectorStoreTextSearch<T>` provides a high-level search API that automatically generates embeddings for queries:
|
|
172
|
+
|
|
173
|
+
```csharp
|
|
174
|
+
using Microsoft.Extensions.VectorData;
|
|
175
|
+
using Microsoft.SemanticKernel.Data;
|
|
176
|
+
|
|
177
|
+
// Create text search from collection + embedding service
|
|
178
|
+
var textSearch = new VectorStoreTextSearch<KnowledgeEntry>(
|
|
179
|
+
collection,
|
|
180
|
+
embeddingGenerationService);
|
|
181
|
+
|
|
182
|
+
// Search with natural language
|
|
183
|
+
var query = "How does Neon branching work?";
|
|
184
|
+
KernelSearchResults<TextSearchResult> results = await textSearch.GetTextSearchResultsAsync(
|
|
185
|
+
query, new() { Top = 5, Skip = 0 });
|
|
186
|
+
|
|
187
|
+
await foreach (TextSearchResult result in results.Results)
|
|
188
|
+
{
|
|
189
|
+
Console.WriteLine($"Name: {result.Name}");
|
|
190
|
+
Console.WriteLine($"Value: {result.Value}");
|
|
191
|
+
Console.WriteLine($"Link: {result.Link}");
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Use as Kernel Plugin (RAG in Prompts)
|
|
196
|
+
|
|
197
|
+
```csharp
|
|
198
|
+
using Microsoft.SemanticKernel;
|
|
199
|
+
using Microsoft.SemanticKernel.Data;
|
|
200
|
+
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
|
|
201
|
+
|
|
202
|
+
var kernel = builder.Build();
|
|
203
|
+
|
|
204
|
+
// Register text search as a plugin
|
|
205
|
+
var searchPlugin = textSearch.CreateWithGetTextSearchResults("KnowledgeSearch");
|
|
206
|
+
kernel.Plugins.Add(searchPlugin);
|
|
207
|
+
|
|
208
|
+
// Use in prompt with automatic retrieval
|
|
209
|
+
var promptTemplate = """
|
|
210
|
+
{{#with (KnowledgeSearch-GetTextSearchResults query)}}
|
|
211
|
+
{{#each this}}
|
|
212
|
+
Name: {{Name}}
|
|
213
|
+
Value: {{Value}}
|
|
214
|
+
---
|
|
215
|
+
{{/each}}
|
|
216
|
+
{{/with}}
|
|
217
|
+
|
|
218
|
+
Based on the above context, answer: {{query}}
|
|
219
|
+
""";
|
|
220
|
+
|
|
221
|
+
var function = KernelFunctionFactory.CreateFromPrompt(
|
|
222
|
+
new PromptTemplateConfig
|
|
223
|
+
{
|
|
224
|
+
Template = promptTemplate,
|
|
225
|
+
TemplateFormat = "handlebars"
|
|
226
|
+
},
|
|
227
|
+
new HandlebarsPromptTemplateFactory());
|
|
228
|
+
|
|
229
|
+
var result = await kernel.InvokeAsync(function,
|
|
230
|
+
new KernelArguments { ["query"] = "How does Neon branching work?" });
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Integration with MAF Agents
|
|
234
|
+
|
|
235
|
+
Use the vector store as a plugin for ChatCompletionAgent to give agents vector memory:
|
|
236
|
+
|
|
237
|
+
```csharp
|
|
238
|
+
using Microsoft.SemanticKernel;
|
|
239
|
+
using Microsoft.SemanticKernel.Agents;
|
|
240
|
+
using Microsoft.SemanticKernel.Data;
|
|
241
|
+
|
|
242
|
+
// Build kernel with vector search plugin
|
|
243
|
+
var kernelBuilder = Kernel.CreateBuilder();
|
|
244
|
+
kernelBuilder.AddOpenAIChatCompletion("gpt-4o", apiKey);
|
|
245
|
+
kernelBuilder.AddOpenAITextEmbeddingGeneration("text-embedding-3-small", apiKey);
|
|
246
|
+
var kernel = kernelBuilder.Build();
|
|
247
|
+
|
|
248
|
+
// Register vector search as a kernel plugin
|
|
249
|
+
var searchPlugin = textSearch.CreateWithGetTextSearchResults("KnowledgeBase");
|
|
250
|
+
kernel.Plugins.Add(searchPlugin);
|
|
251
|
+
|
|
252
|
+
// Create agent with vector memory
|
|
253
|
+
var agent = new ChatCompletionAgent
|
|
254
|
+
{
|
|
255
|
+
Name = "KnowledgeAgent",
|
|
256
|
+
Instructions = """
|
|
257
|
+
You are a knowledge assistant. Use the KnowledgeBase plugin to search for
|
|
258
|
+
relevant information before answering questions. Always cite your sources.
|
|
259
|
+
""",
|
|
260
|
+
Kernel = kernel,
|
|
261
|
+
Arguments = new KernelArguments(
|
|
262
|
+
new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() })
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Agent automatically calls vector search when needed
|
|
266
|
+
await foreach (var message in agent.InvokeAsync(chatHistory))
|
|
267
|
+
{
|
|
268
|
+
Console.WriteLine(message.Content);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Service Registration Summary
|
|
273
|
+
|
|
274
|
+
```csharp
|
|
275
|
+
// Program.cs -- complete registration
|
|
276
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
277
|
+
|
|
278
|
+
// 1. Vector store (Neon + pgvector)
|
|
279
|
+
builder.Services.AddPostgresVectorStore(
|
|
280
|
+
builder.Configuration.GetConnectionString("DefaultConnection")!);
|
|
281
|
+
|
|
282
|
+
// 2. Embedding generation
|
|
283
|
+
builder.Services.AddOpenAITextEmbeddingGeneration(
|
|
284
|
+
modelId: "text-embedding-3-small",
|
|
285
|
+
apiKey: builder.Configuration["OpenAI:ApiKey"]!);
|
|
286
|
+
|
|
287
|
+
// 3. Chat completion (for agents)
|
|
288
|
+
builder.Services.AddOpenAIChatCompletion(
|
|
289
|
+
modelId: "gpt-4o",
|
|
290
|
+
apiKey: builder.Configuration["OpenAI:ApiKey"]!);
|
|
291
|
+
|
|
292
|
+
// 4. Application services
|
|
293
|
+
builder.Services.AddSingleton<EmbeddingService>();
|
|
294
|
+
builder.Services.AddSingleton(sp =>
|
|
295
|
+
{
|
|
296
|
+
var vectorStore = sp.GetRequiredService<PostgresVectorStore>();
|
|
297
|
+
return vectorStore.GetCollection<Guid, KnowledgeEntry>("knowledge_entries");
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Common Mistakes
|
|
302
|
+
|
|
303
|
+
| Wrong | Right | Why |
|
|
304
|
+
|-------|-------|-----|
|
|
305
|
+
| Missing `UseVector()` on data source | Always call `dataSourceBuilder.UseVector()` | Vector type mapping fails without it |
|
|
306
|
+
| Using `float[]` for vector property | `ReadOnlyMemory<float>` with `[VectorStoreVector]` | SK connector expects its own vector types |
|
|
307
|
+
| No `StorageName` on attributes | Set `StorageName` for snake_case columns | C# PascalCase won't match Postgres columns |
|
|
308
|
+
| Creating collection without `EnsureCollectionExistsAsync` | Call before first upsert/search | Table and index won't exist |
|
|
309
|
+
| Hardcoded embedding dimensions | Match `Dimensions` to your model (1536 for ada-002/3-small) | Mismatch causes insertion errors |
|
|
310
|
+
| Using pgvector-dotnet `Vector` with SK | Use `ReadOnlyMemory<float>` | SK has its own type mapping layer |
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Neon Serverless Postgres Standard
|
|
2
|
+
|
|
3
|
+
> **Scope:** neon
|
|
4
|
+
> **Layer:** 0 (always load)
|
|
5
|
+
> **Keywords:** neon, serverless postgres, branching, scale-to-zero, connection string, neonctl
|
|
6
|
+
> **Load When:** neon stack detected
|
|
7
|
+
|
|
8
|
+
Stack: .NET + Neon Serverless Postgres
|
|
9
|
+
|
|
10
|
+
## Core Rules
|
|
11
|
+
|
|
12
|
+
- ALWAYS use pooled connection strings (`-pooler` endpoint) for application traffic
|
|
13
|
+
- ALWAYS use direct connection strings for migrations and schema changes
|
|
14
|
+
- NEVER hardcode connection strings -- use environment variables
|
|
15
|
+
- ALWAYS enable SSL (`SSL Mode=Require`) for all connections
|
|
16
|
+
- ALWAYS configure retry policies for cold-start resilience
|
|
17
|
+
- NEVER leave branches without expiration -- set TTL for dev/CI branches
|
|
18
|
+
|
|
19
|
+
## Neon CLI Setup
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Install neonctl globally
|
|
23
|
+
npm install -g neonctl
|
|
24
|
+
|
|
25
|
+
# Authenticate (opens browser)
|
|
26
|
+
neonctl auth
|
|
27
|
+
|
|
28
|
+
# Verify connection
|
|
29
|
+
neonctl projects list
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Connection Strings
|
|
33
|
+
|
|
34
|
+
### Pooled vs Direct
|
|
35
|
+
|
|
36
|
+
| Type | Hostname Pattern | Use Case |
|
|
37
|
+
|------|-----------------|----------|
|
|
38
|
+
| Pooled | `ep-xxxx-pooler.region.aws.neon.tech` | Application queries, high concurrency |
|
|
39
|
+
| Direct | `ep-xxxx.region.aws.neon.tech` | Migrations, EF Core `dotnet ef`, advisory locks |
|
|
40
|
+
|
|
41
|
+
### Environment Variables
|
|
42
|
+
|
|
43
|
+
| Variable | Purpose |
|
|
44
|
+
|----------|---------|
|
|
45
|
+
| `DATABASE_URL` | Pooled connection for application runtime |
|
|
46
|
+
| `DATABASE_URL_UNPOOLED` | Direct connection for migrations and CLI tools |
|
|
47
|
+
|
|
48
|
+
```ini
|
|
49
|
+
# Pooled (application)
|
|
50
|
+
DATABASE_URL="Host=ep-cool-darkness-123456-pooler.us-east-2.aws.neon.tech;Database=mydb;Username=myuser;Password=secret;SSL Mode=Require"
|
|
51
|
+
|
|
52
|
+
# Direct (migrations)
|
|
53
|
+
DATABASE_URL_UNPOOLED="Host=ep-cool-darkness-123456.us-east-2.aws.neon.tech;Database=mydb;Username=myuser;Password=secret;SSL Mode=Require"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## EF Core Configuration
|
|
57
|
+
|
|
58
|
+
### appsettings.json
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"ConnectionStrings": {
|
|
63
|
+
"DefaultConnection": "Host=ep-cool-darkness-123456-pooler.us-east-2.aws.neon.tech;Database=mydb;Username=myuser;Password=secret;SSL Mode=Require;Trust Server Certificate=true",
|
|
64
|
+
"MigrationConnection": "Host=ep-cool-darkness-123456.us-east-2.aws.neon.tech;Database=mydb;Username=myuser;Password=secret;SSL Mode=Require;Trust Server Certificate=true"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### DbContext Registration
|
|
70
|
+
|
|
71
|
+
```csharp
|
|
72
|
+
builder.Services.AddDbContext<AppDbContext>(options =>
|
|
73
|
+
options.UseNpgsql(
|
|
74
|
+
builder.Configuration.GetConnectionString("DefaultConnection"),
|
|
75
|
+
npgsqlOptions =>
|
|
76
|
+
{
|
|
77
|
+
npgsqlOptions.EnableRetryOnFailure(
|
|
78
|
+
maxRetryCount: 5,
|
|
79
|
+
maxRetryDelay: TimeSpan.FromSeconds(10),
|
|
80
|
+
errorCodesToAdd: null);
|
|
81
|
+
npgsqlOptions.CommandTimeout(30);
|
|
82
|
+
}));
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Design-Time Factory (for migrations using direct connection)
|
|
86
|
+
|
|
87
|
+
```csharp
|
|
88
|
+
public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
|
|
89
|
+
{
|
|
90
|
+
public AppDbContext CreateDbContext(string[] args)
|
|
91
|
+
{
|
|
92
|
+
var configuration = new ConfigurationBuilder()
|
|
93
|
+
.AddJsonFile("appsettings.json")
|
|
94
|
+
.AddEnvironmentVariables()
|
|
95
|
+
.Build();
|
|
96
|
+
|
|
97
|
+
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
|
|
98
|
+
optionsBuilder.UseNpgsql(
|
|
99
|
+
configuration.GetConnectionString("MigrationConnection"));
|
|
100
|
+
|
|
101
|
+
return new AppDbContext(optionsBuilder.Options);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Branching Workflow
|
|
107
|
+
|
|
108
|
+
Neon branches are copy-on-write clones of your database -- instant, zero-cost until data diverges.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# List branches
|
|
112
|
+
neonctl branches list --project-id <project-id>
|
|
113
|
+
|
|
114
|
+
# Create feature branch from main
|
|
115
|
+
neonctl branches create --project-id <project-id> \
|
|
116
|
+
--name feature/my-feature \
|
|
117
|
+
--parent main
|
|
118
|
+
|
|
119
|
+
# Create branch with auto-expiration (CI/CD)
|
|
120
|
+
neonctl branches create --project-id <project-id> \
|
|
121
|
+
--name ci-test-run-42 \
|
|
122
|
+
--parent main \
|
|
123
|
+
--expires-at "2026-04-01T00:00:00Z"
|
|
124
|
+
|
|
125
|
+
# Reset branch to parent state
|
|
126
|
+
neonctl branches reset feature/my-feature --parent --project-id <project-id>
|
|
127
|
+
|
|
128
|
+
# Delete branch
|
|
129
|
+
neonctl branches delete feature/my-feature --project-id <project-id>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Branch Strategy
|
|
133
|
+
|
|
134
|
+
| Branch | Purpose | Expiration |
|
|
135
|
+
|--------|---------|------------|
|
|
136
|
+
| `main` | Production database | Never |
|
|
137
|
+
| `dev` | Development shared | Never |
|
|
138
|
+
| `feature/*` | Feature development | 7 days |
|
|
139
|
+
| `ci-*` | CI test runs | 1 day |
|
|
140
|
+
|
|
141
|
+
## Scale-to-Zero
|
|
142
|
+
|
|
143
|
+
Neon automatically suspends computes after inactivity (default: 5 minutes). This saves cost but introduces cold-start latency (~300-500ms on first request).
|
|
144
|
+
|
|
145
|
+
### Mitigate Cold Starts
|
|
146
|
+
|
|
147
|
+
```csharp
|
|
148
|
+
// 1. Configure retry policy to handle connection drops on wake-up
|
|
149
|
+
builder.Services.AddDbContext<AppDbContext>(options =>
|
|
150
|
+
options.UseNpgsql(connectionString, npgsqlOptions =>
|
|
151
|
+
{
|
|
152
|
+
npgsqlOptions.EnableRetryOnFailure(
|
|
153
|
+
maxRetryCount: 5,
|
|
154
|
+
maxRetryDelay: TimeSpan.FromSeconds(10),
|
|
155
|
+
errorCodesToAdd: null);
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
// 2. Health check to warm the connection pool
|
|
159
|
+
builder.Services.AddHealthChecks()
|
|
160
|
+
.AddNpgSql(connectionString, name: "neon-db");
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Suspend Timeout Configuration
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Set suspend timeout to 10 minutes (600 seconds)
|
|
167
|
+
neonctl branches create --project-id <project-id> \
|
|
168
|
+
--name main \
|
|
169
|
+
--suspend-timeout 600
|
|
170
|
+
|
|
171
|
+
# Compute size range (autoscaling)
|
|
172
|
+
neonctl branches create --project-id <project-id> \
|
|
173
|
+
--name main \
|
|
174
|
+
--cu "0.25-4"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Common Mistakes
|
|
178
|
+
|
|
179
|
+
| Wrong | Right | Why |
|
|
180
|
+
|-------|-------|-----|
|
|
181
|
+
| Direct connection for app queries | Pooled (`-pooler`) connection | Direct connections exhaust compute limits |
|
|
182
|
+
| No retry policy | `EnableRetryOnFailure()` | Cold starts drop first connection |
|
|
183
|
+
| Hardcoded connection strings | Environment variables | Branches have unique endpoints |
|
|
184
|
+
| Permanent CI branches | `--expires-at` TTL | Orphaned branches waste resources |
|
|
185
|
+
| Running migrations on pooled connection | Direct connection for `dotnet ef` | PgBouncer doesn't support advisory locks |
|
|
186
|
+
| No SSL | `SSL Mode=Require` | Neon requires encrypted connections |
|
|
@@ -19,8 +19,8 @@ await mcp__context7__resolve_library_id({ libraryName: "react" });
|
|
|
19
19
|
// Playwright (browser automation)
|
|
20
20
|
await mcp__playwright__browser_navigate({ url: "https://example.com" });
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
//
|
|
22
|
+
// Neon — use `neonctl` CLI + `psql`
|
|
23
|
+
// neonctl connection-string → get DB URL
|
|
24
24
|
// psql <DB_URL> -c "SELECT ..."
|
|
25
25
|
```
|
|
26
26
|
|
|
@@ -85,39 +85,38 @@ const docs = await mcp__context7__query_docs({
|
|
|
85
85
|
|
|
86
86
|
| Tool | Use Case | Example |
|
|
87
87
|
|------|----------|---------|
|
|
88
|
-
| **
|
|
88
|
+
| **Neon CLI (`neonctl`) / psql** | Database schema, tables, relationships, RLS | `psql <DB_URL> -c "SELECT ..."` |
|
|
89
89
|
| **Context7** | Library docs for architecture decisions | `mcp__context7__query_docs()` |
|
|
90
90
|
| **GitHub** | Check existing code patterns, PRs | `mcp__github__search_code()` |
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
93
|
# === SCHEMA ANALYSIS (Critical for contracts.cs) ===
|
|
94
|
-
# Always target local Docker instance — never production.
|
|
95
94
|
|
|
96
|
-
# 0. Get
|
|
97
|
-
|
|
98
|
-
# →
|
|
95
|
+
# 0. Get Neon DB connection string
|
|
96
|
+
neonctl connection-string
|
|
97
|
+
# → e.g.: postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/neondb?sslmode=require
|
|
99
98
|
|
|
100
99
|
# 1. List all tables
|
|
101
|
-
|
|
100
|
+
pg_dump $(neonctl connection-string) --schema-only --schema public 2>/dev/null | grep "CREATE TABLE"
|
|
102
101
|
|
|
103
102
|
# 2. Get schema for each relevant table
|
|
104
|
-
psql $
|
|
103
|
+
psql $(neonctl connection-string) -c "SELECT column_name, data_type, is_nullable, column_default \
|
|
105
104
|
FROM information_schema.columns WHERE table_name = 'leads' ORDER BY ordinal_position"
|
|
106
105
|
|
|
107
106
|
# 3. Get foreign key relationships
|
|
108
|
-
psql $
|
|
107
|
+
psql $(neonctl connection-string) -c "SELECT tc.table_name, kcu.column_name, ccu.table_name AS foreign_table, \
|
|
109
108
|
ccu.column_name AS foreign_column FROM information_schema.table_constraints tc \
|
|
110
109
|
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name \
|
|
111
110
|
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name \
|
|
112
111
|
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = 'leads'"
|
|
113
112
|
|
|
114
113
|
# 4. Check RLS policies (security-critical)
|
|
115
|
-
psql $
|
|
114
|
+
psql $(neonctl connection-string) -c "SELECT tablename, policyname, cmd, qual FROM pg_policies WHERE tablename = 'leads'"
|
|
116
115
|
```
|
|
117
116
|
|
|
118
|
-
**Fallback (
|
|
117
|
+
**Fallback (Neon database not accessible):**
|
|
119
118
|
```
|
|
120
|
-
1. Grep: "\.from\(|\.select\(|SELECT |
|
|
119
|
+
1. Grep: "\.from\(|\.select\(|SELECT |DbSet<" → find query files
|
|
121
120
|
2. Read each query file → extract table/column names
|
|
122
121
|
3. Glob: "src/**/types/**/*.ts" or "**/Entities/**/*.cs" → find type definitions
|
|
123
122
|
4. Read type files → map properties to database columns
|
|
@@ -167,7 +166,7 @@ for (const task of tasks) {
|
|
|
167
166
|
|
|
168
167
|
| Tool | Use Case | Example |
|
|
169
168
|
|------|----------|---------|
|
|
170
|
-
| **
|
|
169
|
+
| **Neon CLI (`neonctl`) / psql** | Run migrations, create RLS policies | `dotnet ef database update` |
|
|
171
170
|
| **GitHub** | Create PR, push branches | `mcp__github__create_pull_request()` |
|
|
172
171
|
| **Context7** | Look up API usage during coding | `mcp__context7__query_docs()` |
|
|
173
172
|
| **Playwright** | Smoke test deployed features, verify UI | `mcp__playwright__browser_navigate()` |
|
|
@@ -176,20 +175,19 @@ for (const task of tasks) {
|
|
|
176
175
|
| **Docker CLI** | Build, run, compose, logs (local) | `docker` commands |
|
|
177
176
|
|
|
178
177
|
```bash
|
|
179
|
-
# ===
|
|
178
|
+
# === NEON (Migrations & RLS via EF Core) ===
|
|
180
179
|
|
|
181
|
-
# Create migration
|
|
182
|
-
|
|
183
|
-
# → Edit the generated SQL file in supabase/migrations/
|
|
180
|
+
# Create migration
|
|
181
|
+
dotnet ef migrations add AddStatusToLeads
|
|
184
182
|
|
|
185
|
-
# Apply migration to
|
|
186
|
-
|
|
183
|
+
# Apply migration to Neon database
|
|
184
|
+
dotnet ef database update
|
|
187
185
|
|
|
188
186
|
# Verify schema after migration
|
|
189
|
-
psql $
|
|
187
|
+
psql $(neonctl connection-string) -c "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'leads'"
|
|
190
188
|
|
|
191
189
|
# Verify RLS policies
|
|
192
|
-
psql $
|
|
190
|
+
psql $(neonctl connection-string) -c "SELECT policyname, cmd, qual FROM pg_policies WHERE tablename = 'leads'"
|
|
193
191
|
```
|
|
194
192
|
|
|
195
193
|
```javascript
|
|
@@ -238,7 +236,7 @@ await mcp__vercel__manage_env_vars({
|
|
|
238
236
|
|
|
239
237
|
| Need | MCP Tool | Native Alternative |
|
|
240
238
|
|------|----------|--------------------|
|
|
241
|
-
| Database schema |
|
|
239
|
+
| Database schema | Neon CLI (`neonctl`) / psql | Grep queries + Read types |
|
|
242
240
|
| Repo metadata | GitHub MCP | Bash `gh` CLI |
|
|
243
241
|
| Library docs | Context7 | WebSearch + WebFetch |
|
|
244
242
|
| Live page preview | Playwright MCP | WebFetch URL |
|
|
@@ -249,7 +247,7 @@ await mcp__vercel__manage_env_vars({
|
|
|
249
247
|
| Cloud resources | Azure CLI (`az resource`, `az deployment`) | — |
|
|
250
248
|
| Vercel deployments | Vercel MCP | Bash `vercel` CLI |
|
|
251
249
|
|
|
252
|
-
**Rule:** MCP tools provide structured data (JSON responses). Native tools require manual parsing. **Always prefer MCP when available** — fall back to native when not. **Exceptions:**
|
|
250
|
+
**Rule:** MCP tools provide structured data (JSON responses). Native tools require manual parsing. **Always prefer MCP when available** — fall back to native when not. **Exceptions:** Neon, Docker, and Azure always use their respective CLIs directly — never MCP.
|
|
253
251
|
|
|
254
252
|
---
|
|
255
253
|
|
|
@@ -345,26 +343,26 @@ await mcp__playwright__browser_file_upload({ ref: "s1e20", paths: ["/path/to/fil
|
|
|
345
343
|
|
|
346
344
|
---
|
|
347
345
|
|
|
348
|
-
###
|
|
346
|
+
### Neon: Full Schema Discovery (via neonctl + psql)
|
|
349
347
|
|
|
350
|
-
> **Primary:**
|
|
351
|
-
> **
|
|
348
|
+
> **Primary:** Neon CLI (`neonctl`) + psql connecting to Neon database.
|
|
349
|
+
> **Connection:** Always use `neonctl connection-string` to get the DB URL.
|
|
352
350
|
|
|
353
351
|
```bash
|
|
354
352
|
# Complete workflow for Phase 3 schema analysis
|
|
355
353
|
|
|
356
|
-
# 1. Get
|
|
357
|
-
|
|
354
|
+
# 1. Get Neon DB URL
|
|
355
|
+
neonctl connection-string # → pooled connection string
|
|
358
356
|
|
|
359
357
|
# 2. List all tables
|
|
360
|
-
|
|
358
|
+
pg_dump $(neonctl connection-string) --schema-only --schema public 2>/dev/null | grep "CREATE TABLE"
|
|
361
359
|
|
|
362
360
|
# 3. For each relevant table:
|
|
363
|
-
psql $
|
|
361
|
+
psql $(neonctl connection-string) -c "SELECT column_name, data_type, is_nullable, column_default \
|
|
364
362
|
FROM information_schema.columns WHERE table_name = 'TABLE_NAME' ORDER BY ordinal_position"
|
|
365
363
|
|
|
366
364
|
# 4. Get relationships
|
|
367
|
-
psql $
|
|
365
|
+
psql $(neonctl connection-string) -c "SELECT tc.table_name, kcu.column_name, ccu.table_name AS foreign_table \
|
|
368
366
|
FROM information_schema.table_constraints tc \
|
|
369
367
|
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name \
|
|
370
368
|
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name \
|
|
@@ -397,7 +395,7 @@ const docs = await mcp__context7__query_docs({
|
|
|
397
395
|
```javascript
|
|
398
396
|
// Find patterns in large codebase during Phase 2
|
|
399
397
|
const results = await mcp__github__search_code({
|
|
400
|
-
query: "
|
|
398
|
+
query: "DbSet leads repo:myorg/myrepo",
|
|
401
399
|
});
|
|
402
400
|
// → Find all places that query the leads table
|
|
403
401
|
```
|
|
@@ -407,7 +405,7 @@ const results = await mcp__github__search_code({
|
|
|
407
405
|
## Security Considerations
|
|
408
406
|
|
|
409
407
|
- **Never pass secrets** in MCP tool parameters (API keys, tokens, passwords)
|
|
410
|
-
- **
|
|
408
|
+
- **Neon** uses neonctl/psql — use database branches for development, not production
|
|
411
409
|
- **GitHub MCP** inherits the configured token's permissions
|
|
412
410
|
- **Always validate tool responses** before using in contracts or code generation
|
|
413
411
|
|