@polymorphism-tech/morph-spec 4.15.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 +4 -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 +2 -1
- package/framework/agents/backend/neon-expert.md +397 -0
- package/framework/agents/frontend/gsap-animator.md +131 -0
- package/framework/agents.json +1175 -1077
- 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 +30 -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/project/setup-infra-cmd.js +1 -1
- 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/src/scripts/setup-infra.js +60 -30
- 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
|
@@ -26,7 +26,7 @@ export function loadMcpRegistry() {
|
|
|
26
26
|
/**
|
|
27
27
|
* Filter MCPs by stack relevance
|
|
28
28
|
* @param {Object} registry - Full MCP registry
|
|
29
|
-
* @param {string} stack - Detected project stack (e.g., 'nextjs-
|
|
29
|
+
* @param {string} stack - Detected project stack (e.g., 'nextjs-neon', 'blazor')
|
|
30
30
|
* @returns {Object} Filtered map of mcpName → mcpEntry
|
|
31
31
|
*/
|
|
32
32
|
export function filterByStack(registry, stack) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Task classifier — identifies test/validation tasks by title.
|
|
2
|
+
* Task classifier — identifies test/validation and infrastructure tasks by title.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const TEST_TASK_PATTERN = /\b(tests?|testing|specs?|coverage|e2e|unit|integration|integra[cç][aã]o|valida[cç][aã]o|cobertura|testes?)\b/i;
|
|
@@ -14,3 +14,17 @@ export function isTestTask(taskTitle) {
|
|
|
14
14
|
if (!taskTitle) return false;
|
|
15
15
|
return TEST_TASK_PATTERN.test(taskTitle);
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
const INFRA_TASK_PATTERN = /\b(infrastructure|infra|cleanup|migration|migra[cç][aã]o|config|setup|ci\/cd|devops|deploy|docker|packages?|dependencies|tags)\b/i;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if the task title indicates an infrastructure/cleanup/migration task.
|
|
22
|
+
* Used to skip contract validation and suppress breaking change warnings.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} taskTitle
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
export function isInfrastructureTask(taskTitle) {
|
|
28
|
+
if (!taskTitle) return false;
|
|
29
|
+
return INFRA_TASK_PATTERN.test(taskTitle);
|
|
30
|
+
}
|
|
@@ -68,9 +68,13 @@ export function runTestSuite(projectPath = '.', opts = {}) {
|
|
|
68
68
|
});
|
|
69
69
|
return { passed: true, output: output ?? '', exitCode: 0 };
|
|
70
70
|
} catch (err) {
|
|
71
|
+
const output = (err.stdout || '') + (err.stderr || '');
|
|
72
|
+
const BUILD_FAIL = /Build FAILED|error MSB\d+|error CS\d+|Could not find.*\.csproj/i;
|
|
73
|
+
const buildFailed = BUILD_FAIL.test(output);
|
|
71
74
|
return {
|
|
72
75
|
passed: false,
|
|
73
|
-
|
|
76
|
+
buildFailed,
|
|
77
|
+
output,
|
|
74
78
|
exitCode: err.status ?? 1
|
|
75
79
|
};
|
|
76
80
|
}
|
|
@@ -23,18 +23,17 @@ export function validateSpecContent(specPath) {
|
|
|
23
23
|
|
|
24
24
|
const content = readFileSync(specPath, 'utf8');
|
|
25
25
|
|
|
26
|
-
// Required sections
|
|
27
|
-
// "## Functional Requirements" and "## Technical Architecture")
|
|
26
|
+
// Required sections (flexible regex handles numbered variants like '## 1. Overview')
|
|
28
27
|
const requiredSections = [
|
|
29
|
-
'## Overview',
|
|
30
|
-
'## Functional Requirements',
|
|
31
|
-
'## Technical',
|
|
32
|
-
'## Data Model',
|
|
28
|
+
{ label: '## Overview', pattern: /^##\s+(?:\d+\.?\s+)?Overview/i },
|
|
29
|
+
{ label: '## Functional Requirements', pattern: /^##\s+(?:\d+\.?\s+)?Functional Requirements/i },
|
|
30
|
+
{ label: '## Technical', pattern: /^##\s+(?:\d+\.?\s+)?Technical/i },
|
|
31
|
+
{ label: '## Data Model', pattern: /^##\s+(?:\d+\.?\s+)?Data Model/i },
|
|
33
32
|
];
|
|
34
33
|
|
|
35
34
|
const lines = content.split('\n');
|
|
36
|
-
const missing = requiredSections.filter(
|
|
37
|
-
!lines.some(line =>
|
|
35
|
+
const missing = requiredSections.filter(s =>
|
|
36
|
+
!lines.some(line => s.pattern.test(line))
|
|
38
37
|
);
|
|
39
38
|
|
|
40
39
|
// Additional quality checks
|
|
@@ -44,8 +43,8 @@ export function validateSpecContent(specPath) {
|
|
|
44
43
|
// Missing sections are warnings, not errors — not every spec needs all sections
|
|
45
44
|
// (e.g., frontend-only features may not have Data Model, CLI tools may not have Technical Architecture)
|
|
46
45
|
if (missing.length > 0) {
|
|
47
|
-
missing.forEach(
|
|
48
|
-
warnings.push(`Missing recommended section: ${
|
|
46
|
+
missing.forEach(s => {
|
|
47
|
+
warnings.push(`Missing recommended section: ${s.label}`);
|
|
49
48
|
});
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -119,11 +119,13 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// Always run contract compliance if the contracts file exists (stack-aware)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
if (!options.skipContracts) {
|
|
123
|
+
const _profileForContracts = getStackProfile(projectPath);
|
|
124
|
+
const contractsType = resolveContractsOutputType(_profileForContracts.stack);
|
|
125
|
+
const contractsPath = getAbsoluteOutputPath(projectPath, featureName, contractsType);
|
|
126
|
+
if (existsSync(contractsPath) && !_profileForContracts.isNextjs) {
|
|
127
|
+
validatorIds.push('contract-compliance');
|
|
128
|
+
}
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
// Deduplicate
|
|
@@ -79,19 +79,75 @@ export function installRulesForStack(rulesSrc, rulesDest, projectTags) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Re-install only stack-aware assets (rules, skills, agents) using current config.json.
|
|
84
|
+
* Skips infrastructure steps (dirs, framework copy, hooks).
|
|
85
|
+
*
|
|
86
|
+
* @param {string} targetPath - Absolute path to the target project directory
|
|
87
|
+
* @param {Function} log - Logging function
|
|
88
|
+
* @returns {{ agentCounts: object, domainCounts: object, projectTags: string[] }}
|
|
89
|
+
*/
|
|
90
|
+
async function installStackAwareAssets(targetPath, log) {
|
|
91
|
+
const configPath = join(targetPath, '.morph', 'config', 'config.json');
|
|
92
|
+
let projectTags = [];
|
|
93
|
+
if (await pathExists(configPath)) {
|
|
94
|
+
try {
|
|
95
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
96
|
+
projectTags = getProjectTags(config);
|
|
97
|
+
} catch { /* ignore malformed config */ }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
log('Installing stack-filtered rules...');
|
|
101
|
+
const claudeDest = join(targetPath, '.claude');
|
|
102
|
+
const rulesSrc = join(FRAMEWORK_DIR, 'rules');
|
|
103
|
+
const rulesDest = join(claudeDest, 'rules');
|
|
104
|
+
if (await pathExists(rulesSrc)) {
|
|
105
|
+
await ensureDir(rulesDest);
|
|
106
|
+
installRulesForStack(rulesSrc, rulesDest, projectTags);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
log('Installing skills...');
|
|
110
|
+
await installSkills(targetPath, { projectTags });
|
|
111
|
+
|
|
112
|
+
log('Installing agents...');
|
|
113
|
+
const agentCounts = await installAgents(targetPath, FRAMEWORK_DIR, { projectTags });
|
|
114
|
+
|
|
115
|
+
log('Installing domain agents...');
|
|
116
|
+
const domainCounts = await installDomainAgents(targetPath, FRAMEWORK_DIR, { projectTags });
|
|
117
|
+
|
|
118
|
+
return { agentCounts, domainCounts, projectTags };
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
/**
|
|
83
122
|
* Installs MORPH-SPEC infrastructure into the target project directory.
|
|
84
123
|
* Headless — no prompts, no spinner (suppressed when MORPH_QUIET=1), no stack detection.
|
|
85
124
|
*
|
|
86
125
|
* @param {string} targetPath - Absolute path to the target project directory
|
|
126
|
+
* @param {object} options
|
|
127
|
+
* @param {Function} options._exec - exec function (for testing)
|
|
128
|
+
* @param {boolean} options.reinstall - If true, only re-install stack-aware assets (rules, skills, agents)
|
|
87
129
|
*/
|
|
88
|
-
export async function setupInfra(targetPath, { _exec = execSync } = {}) {
|
|
130
|
+
export async function setupInfra(targetPath, { _exec = execSync, reinstall = false } = {}) {
|
|
89
131
|
const quiet = process.env.MORPH_QUIET === '1';
|
|
90
132
|
|
|
91
133
|
function log(msg) {
|
|
92
134
|
if (!quiet) process.stdout.write(msg + '\n');
|
|
93
135
|
}
|
|
94
136
|
|
|
137
|
+
// --- Reinstall mode: only re-install stack-aware assets ---
|
|
138
|
+
if (reinstall) {
|
|
139
|
+
log('setup-infra (reinstall): re-installing stack-aware assets...');
|
|
140
|
+
const { agentCounts, domainCounts } = await installStackAwareAssets(targetPath, log);
|
|
141
|
+
log('setup-infra (reinstall): complete.');
|
|
142
|
+
return {
|
|
143
|
+
agents: {
|
|
144
|
+
tier1: agentCounts?.tier1 ?? 0,
|
|
145
|
+
tier2: agentCounts?.tier2 ?? 0,
|
|
146
|
+
specialists: domainCounts?.specialists ?? 0,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
95
151
|
// --- 0. Install required Claude Code plugins ---
|
|
96
152
|
await installRequiredPlugins(log, _exec);
|
|
97
153
|
|
|
@@ -188,35 +244,9 @@ export async function setupInfra(targetPath, { _exec = execSync } = {}) {
|
|
|
188
244
|
await copyDirectory(commandsSrc, commandsDest);
|
|
189
245
|
}
|
|
190
246
|
|
|
191
|
-
// ---
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
196
|
-
projectTags = getProjectTags(config);
|
|
197
|
-
} catch { /* ignore malformed config */ }
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// --- 10. Copy framework/rules → .claude/rules (filtered by stack) ---
|
|
201
|
-
log('Step 10: Installing path-scoped rules...');
|
|
202
|
-
const rulesSrc = join(FRAMEWORK_DIR, 'rules');
|
|
203
|
-
const rulesDest = join(claudeDest, 'rules');
|
|
204
|
-
if (await pathExists(rulesSrc)) {
|
|
205
|
-
await ensureDir(rulesDest);
|
|
206
|
-
installRulesForStack(rulesSrc, rulesDest, projectTags);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// --- 11. installSkills (filtered by stack) ---
|
|
210
|
-
log('Step 11: Installing skills...');
|
|
211
|
-
await installSkills(targetPath, { projectTags });
|
|
212
|
-
|
|
213
|
-
// --- 12. installAgents ---
|
|
214
|
-
log('Step 12: Installing agents...');
|
|
215
|
-
const agentCounts = await installAgents(targetPath, FRAMEWORK_DIR, { projectTags });
|
|
216
|
-
|
|
217
|
-
// --- 13. installDomainAgents (filtered by stack) ---
|
|
218
|
-
log('Step 13: Installing domain agents...');
|
|
219
|
-
const domainCounts = await installDomainAgents(targetPath, FRAMEWORK_DIR, { projectTags });
|
|
247
|
+
// --- 10-13. Install stack-aware assets (rules, skills, agents) ---
|
|
248
|
+
log('Steps 10-13: Installing stack-aware assets...');
|
|
249
|
+
const { agentCounts, domainCounts } = await installStackAwareAssets(targetPath, log);
|
|
220
250
|
|
|
221
251
|
// --- 14. Copy framework/CLAUDE.md → .claude/CLAUDE.md (morph-owned, gitignored) ---
|
|
222
252
|
log('Step 14: Installing .claude/CLAUDE.md...');
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: supabase-expert
|
|
3
|
-
description: Supabase & PostgreSQL specialist for RLS policies, auth integration, pgvector, Realtime subscriptions, Edge Functions, and migration management. Use when designing database schemas, implementing Row Level Security, setting up auth flows, or working with pgvector embeddings.
|
|
4
|
-
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
-
stacks:
|
|
6
|
-
- supabase
|
|
7
|
-
color: teal
|
|
8
|
-
effort: medium
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Supabase & PostgreSQL Specialist
|
|
12
|
-
|
|
13
|
-
Expert in Supabase auth, Row Level Security, Realtime, Edge Functions, and pgvector.
|
|
14
|
-
|
|
15
|
-
> **Ref:** `framework/standards/infrastructure/supabase/` — RLS, auth, storage, pgvector standards
|
|
16
|
-
|
|
17
|
-
## Connection Pooling
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
# Direct connection (migrations only)
|
|
21
|
-
postgresql://postgres:[password]@db.[project].supabase.co:5432/postgres
|
|
22
|
-
|
|
23
|
-
# PgBouncer (application — ALWAYS use this)
|
|
24
|
-
postgresql://postgres.[project]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**Rule:** Application code ALWAYS connects via PgBouncer (port 6543). Direct connections (port 5432) only for migrations and schema changes.
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Row Level Security (RLS)
|
|
32
|
-
|
|
33
|
-
### Enable RLS on every table
|
|
34
|
-
|
|
35
|
-
```sql
|
|
36
|
-
ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Common RLS Patterns
|
|
40
|
-
|
|
41
|
-
```sql
|
|
42
|
-
-- User owns the row
|
|
43
|
-
CREATE POLICY "Users can view own documents"
|
|
44
|
-
ON public.documents FOR SELECT
|
|
45
|
-
USING (auth.uid() = user_id);
|
|
46
|
-
|
|
47
|
-
-- User can insert own rows
|
|
48
|
-
CREATE POLICY "Users can insert own documents"
|
|
49
|
-
ON public.documents FOR INSERT
|
|
50
|
-
WITH CHECK (auth.uid() = user_id);
|
|
51
|
-
|
|
52
|
-
-- User can update own rows
|
|
53
|
-
CREATE POLICY "Users can update own documents"
|
|
54
|
-
ON public.documents FOR UPDATE
|
|
55
|
-
USING (auth.uid() = user_id)
|
|
56
|
-
WITH CHECK (auth.uid() = user_id);
|
|
57
|
-
|
|
58
|
-
-- User can delete own rows
|
|
59
|
-
CREATE POLICY "Users can delete own documents"
|
|
60
|
-
ON public.documents FOR DELETE
|
|
61
|
-
USING (auth.uid() = user_id);
|
|
62
|
-
|
|
63
|
-
-- Service role bypass (for backend API)
|
|
64
|
-
CREATE POLICY "Service role full access"
|
|
65
|
-
ON public.documents FOR ALL
|
|
66
|
-
USING (auth.role() = 'service_role');
|
|
67
|
-
|
|
68
|
-
-- Team-based access
|
|
69
|
-
CREATE POLICY "Team members can view"
|
|
70
|
-
ON public.documents FOR SELECT
|
|
71
|
-
USING (
|
|
72
|
-
team_id IN (
|
|
73
|
-
SELECT team_id FROM public.team_members
|
|
74
|
-
WHERE user_id = auth.uid()
|
|
75
|
-
)
|
|
76
|
-
);
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### RLS with Clerk JWT (custom claims)
|
|
80
|
-
|
|
81
|
-
```sql
|
|
82
|
-
-- Extract Clerk user ID from JWT
|
|
83
|
-
CREATE OR REPLACE FUNCTION auth.clerk_user_id()
|
|
84
|
-
RETURNS TEXT AS $$
|
|
85
|
-
SELECT coalesce(
|
|
86
|
-
current_setting('request.jwt.claims', true)::json->>'sub',
|
|
87
|
-
''
|
|
88
|
-
);
|
|
89
|
-
$$ LANGUAGE sql STABLE;
|
|
90
|
-
|
|
91
|
-
-- Use in RLS policies
|
|
92
|
-
CREATE POLICY "Clerk users own rows"
|
|
93
|
-
ON public.documents FOR ALL
|
|
94
|
-
USING (clerk_user_id = auth.clerk_user_id());
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Migrations
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
# Create new migration
|
|
103
|
-
npx supabase migration new create_documents_table
|
|
104
|
-
|
|
105
|
-
# Apply locally
|
|
106
|
-
npx supabase db reset
|
|
107
|
-
|
|
108
|
-
# Push to remote
|
|
109
|
-
npx supabase db push
|
|
110
|
-
|
|
111
|
-
# Diff local vs remote
|
|
112
|
-
npx supabase db diff --use-migra
|
|
113
|
-
|
|
114
|
-
# Lint for common issues
|
|
115
|
-
npx supabase db lint --level warning
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Migration file structure
|
|
119
|
-
|
|
120
|
-
```sql
|
|
121
|
-
-- supabase/migrations/20240101000000_create_documents_table.sql
|
|
122
|
-
|
|
123
|
-
CREATE TABLE public.documents (
|
|
124
|
-
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
125
|
-
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
126
|
-
title TEXT NOT NULL,
|
|
127
|
-
content TEXT,
|
|
128
|
-
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
129
|
-
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
-- Always enable RLS
|
|
133
|
-
ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;
|
|
134
|
-
|
|
135
|
-
-- Always add indexes for filtered columns
|
|
136
|
-
CREATE INDEX idx_documents_user_id ON public.documents(user_id);
|
|
137
|
-
CREATE INDEX idx_documents_created_at ON public.documents(created_at DESC);
|
|
138
|
-
|
|
139
|
-
-- RLS policies
|
|
140
|
-
CREATE POLICY "Users manage own documents"
|
|
141
|
-
ON public.documents FOR ALL
|
|
142
|
-
USING (auth.uid() = user_id)
|
|
143
|
-
WITH CHECK (auth.uid() = user_id);
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
## pgvector (Embeddings)
|
|
149
|
-
|
|
150
|
-
### Setup
|
|
151
|
-
|
|
152
|
-
```sql
|
|
153
|
-
CREATE EXTENSION IF NOT EXISTS vector;
|
|
154
|
-
|
|
155
|
-
CREATE TABLE public.embeddings (
|
|
156
|
-
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
157
|
-
document_id UUID REFERENCES public.documents(id) ON DELETE CASCADE,
|
|
158
|
-
content TEXT NOT NULL,
|
|
159
|
-
embedding vector(1536) NOT NULL, -- OpenAI text-embedding-3-small
|
|
160
|
-
metadata JSONB DEFAULT '{}'::jsonb,
|
|
161
|
-
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
-- HNSW index for fast similarity search (cosine distance)
|
|
165
|
-
CREATE INDEX idx_embeddings_vector ON public.embeddings
|
|
166
|
-
USING hnsw (embedding vector_cosine_ops)
|
|
167
|
-
WITH (m = 16, ef_construction = 64);
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Similarity search function
|
|
171
|
-
|
|
172
|
-
```sql
|
|
173
|
-
CREATE OR REPLACE FUNCTION match_embeddings(
|
|
174
|
-
query_embedding vector(1536),
|
|
175
|
-
match_threshold FLOAT DEFAULT 0.78,
|
|
176
|
-
match_count INT DEFAULT 5
|
|
177
|
-
)
|
|
178
|
-
RETURNS TABLE (
|
|
179
|
-
id UUID,
|
|
180
|
-
content TEXT,
|
|
181
|
-
metadata JSONB,
|
|
182
|
-
similarity FLOAT
|
|
183
|
-
)
|
|
184
|
-
LANGUAGE plpgsql AS $$
|
|
185
|
-
BEGIN
|
|
186
|
-
RETURN QUERY
|
|
187
|
-
SELECT
|
|
188
|
-
e.id,
|
|
189
|
-
e.content,
|
|
190
|
-
e.metadata,
|
|
191
|
-
1 - (e.embedding <=> query_embedding) AS similarity
|
|
192
|
-
FROM public.embeddings e
|
|
193
|
-
WHERE 1 - (e.embedding <=> query_embedding) > match_threshold
|
|
194
|
-
ORDER BY e.embedding <=> query_embedding
|
|
195
|
-
LIMIT match_count;
|
|
196
|
-
END;
|
|
197
|
-
$$;
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
|
-
## Realtime Subscriptions
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
// Subscribe to changes on a table
|
|
206
|
-
const channel = supabase
|
|
207
|
-
.channel('documents-changes')
|
|
208
|
-
.on('postgres_changes',
|
|
209
|
-
{ event: '*', schema: 'public', table: 'documents', filter: `user_id=eq.${userId}` },
|
|
210
|
-
(payload) => {
|
|
211
|
-
console.log('Change:', payload.eventType, payload.new);
|
|
212
|
-
}
|
|
213
|
-
)
|
|
214
|
-
.subscribe();
|
|
215
|
-
|
|
216
|
-
// Cleanup
|
|
217
|
-
channel.unsubscribe();
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**Rule:** Always filter subscriptions by user/team — never subscribe to full table changes.
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
## Storage (File Uploads)
|
|
225
|
-
|
|
226
|
-
```sql
|
|
227
|
-
-- Create bucket
|
|
228
|
-
INSERT INTO storage.buckets (id, name, public)
|
|
229
|
-
VALUES ('avatars', 'avatars', true);
|
|
230
|
-
|
|
231
|
-
-- RLS for storage
|
|
232
|
-
CREATE POLICY "Users upload own avatar"
|
|
233
|
-
ON storage.objects FOR INSERT
|
|
234
|
-
WITH CHECK (
|
|
235
|
-
bucket_id = 'avatars' AND
|
|
236
|
-
auth.uid()::text = (storage.foldername(name))[1]
|
|
237
|
-
);
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
## Checklist
|
|
243
|
-
|
|
244
|
-
- [ ] PgBouncer (port 6543) used for all application connections
|
|
245
|
-
- [ ] RLS enabled on ALL public tables
|
|
246
|
-
- [ ] RLS policies cover SELECT, INSERT, UPDATE, DELETE
|
|
247
|
-
- [ ] Service role policy exists for backend API access
|
|
248
|
-
- [ ] Migrations use `npx supabase migration new` (not manual SQL)
|
|
249
|
-
- [ ] Indexes on all frequently filtered columns
|
|
250
|
-
- [ ] pgvector: HNSW index with appropriate m/ef_construction values
|
|
251
|
-
- [ ] Realtime subscriptions filtered by user/team
|
|
252
|
-
- [ ] Storage buckets have RLS policies
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths:
|
|
3
|
-
- "**/*.sql"
|
|
4
|
-
- "**/supabase/**"
|
|
5
|
-
- "**/migrations/**"
|
|
6
|
-
stacks:
|
|
7
|
-
- supabase
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Supabase & PostgreSQL Standards
|
|
11
|
-
|
|
12
|
-
@.morph/framework/standards/infrastructure/supabase/supabase-auth.md
|
|
13
|
-
@.morph/framework/standards/infrastructure/supabase/supabase-rls.md
|
|
14
|
-
@.morph/framework/standards/infrastructure/supabase/supabase-storage.md
|
|
15
|
-
@.morph/framework/standards/infrastructure/supabase/supabase-pgvector.md
|
|
16
|
-
@.morph/framework/standards/infrastructure/supabase/mcp-setup.md
|
|
17
|
-
@.morph/framework/standards/infrastructure/supabase/supabase-custom-jwt.md
|
|
18
|
-
@.morph/framework/standards/backend/database/postgresql/database.md
|
|
19
|
-
|
|
20
|
-
## SQL & Migration Templates
|
|
21
|
-
|
|
22
|
-
| Template | Path | Purpose |
|
|
23
|
-
|----------|------|---------|
|
|
24
|
-
| Migration (full) | `.morph/framework/templates/code/sql/supabase-migration.template.sql` | RLS + indexes |
|
|
25
|
-
| Migration (simple) | `.morph/framework/templates/code/sql/supabase-migration.sql` | Basic migration |
|
|
26
|
-
| RLS Policy | `.morph/framework/templates/code/sql/rls-policy.sql` | Row Level Security |
|
|
27
|
-
| TS Types | `.morph/framework/templates/code/typescript/contracts.ts` | Supabase DB types |
|
|
28
|
-
|
|
29
|
-
Read the template to see the exact pattern for migrations, RLS policies, and type generation.
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# Supabase Local Development Guide
|
|
2
|
-
|
|
3
|
-
> **Scope:** nextjs-supabase
|
|
4
|
-
> **Layer:** 2 (on keyword)
|
|
5
|
-
> **Keywords:** supabase, cli, local, docker, setup, migrations, psql
|
|
6
|
-
> **Load When:** supabase setup keywords detected
|
|
7
|
-
|
|
8
|
-
Local-first database management via Supabase CLI and psql — always targeting the local Docker instance.
|
|
9
|
-
|
|
10
|
-
## CRITICAL WARNING
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
==========================================================
|
|
14
|
-
NEVER connect to a PRODUCTION Supabase project.
|
|
15
|
-
|
|
16
|
-
All database operations MUST target the local Docker
|
|
17
|
-
instance started via `npx supabase start`.
|
|
18
|
-
|
|
19
|
-
Production schema may diverge from local (renamed tables,
|
|
20
|
-
columns, migrations). Local is the single source of truth
|
|
21
|
-
during development.
|
|
22
|
-
==========================================================
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Setup
|
|
26
|
-
|
|
27
|
-
### 1. Install Supabase CLI
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npm install -g supabase
|
|
31
|
-
# or use npx (no global install needed)
|
|
32
|
-
npx supabase --version
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### 2. Start Local Instance
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
npx supabase start
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
This starts PostgreSQL, GoTrue (auth), Storage, and other services in Docker containers.
|
|
42
|
-
|
|
43
|
-
### 3. Get Connection Details
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
npx supabase status
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Output includes:
|
|
50
|
-
- **DB URL**: `postgresql://postgres:postgres@127.0.0.1:54322/postgres` (port may vary)
|
|
51
|
-
- **API URL**: `http://127.0.0.1:54321`
|
|
52
|
-
- **anon key** and **service_role key** for local testing
|
|
53
|
-
|
|
54
|
-
> **Important:** The port (54322) may vary when running multiple Supabase instances. Always use `npx supabase status` to get the current DB URL — never hardcode it.
|
|
55
|
-
|
|
56
|
-
## Common CLI Commands
|
|
57
|
-
|
|
58
|
-
### Schema Inspection
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
# Full schema dump
|
|
62
|
-
npx supabase db dump --local --schema public
|
|
63
|
-
|
|
64
|
-
# List tables (from dump output)
|
|
65
|
-
npx supabase db dump --local --schema public 2>/dev/null | grep "CREATE TABLE"
|
|
66
|
-
|
|
67
|
-
# Detailed table schema via psql
|
|
68
|
-
psql $DB_URL -c "SELECT column_name, data_type, is_nullable, column_default \
|
|
69
|
-
FROM information_schema.columns WHERE table_name = 'TABLE_NAME' ORDER BY ordinal_position"
|
|
70
|
-
|
|
71
|
-
# Foreign key relationships
|
|
72
|
-
psql $DB_URL -c "SELECT tc.table_name, kcu.column_name, ccu.table_name AS foreign_table, \
|
|
73
|
-
ccu.column_name AS foreign_column FROM information_schema.table_constraints tc \
|
|
74
|
-
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name \
|
|
75
|
-
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name \
|
|
76
|
-
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = 'TABLE_NAME'"
|
|
77
|
-
|
|
78
|
-
# RLS policies
|
|
79
|
-
psql $DB_URL -c "SELECT tablename, policyname, cmd, qual FROM pg_policies WHERE schemaname = 'public'"
|
|
80
|
-
|
|
81
|
-
# Indexes
|
|
82
|
-
psql $DB_URL -c "SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'TABLE_NAME'"
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Migrations
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
# Create a new migration
|
|
89
|
-
npx supabase migration new add_status_to_leads
|
|
90
|
-
|
|
91
|
-
# Apply pending migrations to local
|
|
92
|
-
npx supabase migration up --local
|
|
93
|
-
|
|
94
|
-
# List applied migrations
|
|
95
|
-
npx supabase migration list --local
|
|
96
|
-
|
|
97
|
-
# Reset local database (destructive — re-applies all migrations)
|
|
98
|
-
npx supabase db reset
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Data Operations
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
# Run arbitrary SQL against local
|
|
105
|
-
psql $DB_URL -c "SELECT * FROM leads LIMIT 5"
|
|
106
|
-
|
|
107
|
-
# Seed data
|
|
108
|
-
npx supabase db seed --local
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Usage with MORPH-SPEC Workflow
|
|
112
|
-
|
|
113
|
-
During MORPH-SPEC phases, the Supabase CLI is used for:
|
|
114
|
-
|
|
115
|
-
| Phase | CLI Usage | Commands |
|
|
116
|
-
|-------|-----------|----------|
|
|
117
|
-
| **Phase 2 (Design)** | Inspect existing schema to inform spec | `npx supabase status` → `psql` queries |
|
|
118
|
-
| **Phase 4 (Tasks)** | Verify migration dependencies | `npx supabase migration list --local` |
|
|
119
|
-
| **Phase 5 (Implement)** | Apply migrations, verify RLS, test queries | `npx supabase migration up --local` + `psql` |
|
|
120
|
-
| **Post-Implementation** | Verify RLS policies are correct | `psql` pg_policies queries |
|
|
121
|
-
|
|
122
|
-
### Example Workflow
|
|
123
|
-
|
|
124
|
-
```
|
|
125
|
-
1. Design phase: `npx supabase status` → `psql $DB_URL` to inspect schema
|
|
126
|
-
2. Write migration SQL in supabase/migrations/
|
|
127
|
-
3. Apply: `npx supabase migration up --local`
|
|
128
|
-
4. Verify: `psql $DB_URL -c "SELECT policyname, cmd FROM pg_policies WHERE tablename = 'search_logs'"`
|
|
129
|
-
5. Test: `psql $DB_URL -c "SELECT * FROM search_logs LIMIT 5"`
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Best Practices
|
|
133
|
-
|
|
134
|
-
1. **Always use local Supabase** (`npx supabase start`) — never connect to remote/production
|
|
135
|
-
2. **Use `npx supabase status`** to detect the DB URL dynamically — port may vary
|
|
136
|
-
3. **Write migrations as SQL files** in `supabase/migrations/` — apply via CLI
|
|
137
|
-
4. **Verify RLS after every table change** — missing policies are a CRITICAL security issue
|
|
138
|
-
5. **Reset local DB when needed** — `npx supabase db reset` re-applies all migrations cleanly
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
*MORPH-SPEC by Polymorphism Tech*
|