@rigstate/mcp 0.7.5 → 0.7.7
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/dist/index.js +308 -139
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/arch-analysis.ts +171 -0
- package/src/lib/curator/actions/submit.ts +8 -9
- package/src/lib/project-context-utils.ts +148 -0
- package/src/lib/types-roadmap.ts +43 -0
- package/src/lib/types.ts +41 -25
- package/src/server/types.ts +1 -1
- package/src/tools/analyze-database-performance.ts +12 -0
- package/src/tools/archaeological-scan.ts +37 -214
- package/src/tools/audit-integrity-gate.ts +16 -4
- package/src/tools/check-agent-bridge.ts +11 -0
- package/src/tools/check-rules-sync.ts +13 -0
- package/src/tools/complete-roadmap-task.ts +12 -0
- package/src/tools/generate-professional-pdf.ts +17 -2
- package/src/tools/get-next-roadmap-step.ts +5 -1
- package/src/tools/get-project-context.ts +17 -79
- package/src/tools/list-features.ts +20 -24
- package/src/tools/list-roadmap-tasks.ts +6 -1
- package/src/tools/pending-tasks.ts +22 -0
- package/src/tools/planning-tools.ts +10 -14
- package/src/tools/query-brain.ts +37 -50
- package/src/tools/research-tools.ts +9 -8
- package/src/tools/run-architecture-audit.ts +8 -9
- package/src/tools/save-decision.ts +15 -9
- package/src/tools/security-tools.ts +26 -2
- package/src/tools/submit-idea.ts +15 -9
- package/src/tools/sync-ide-rules.ts +16 -3
- package/src/tools/teacher-mode.ts +6 -7
- package/src/tools/update-roadmap.ts +14 -8
|
@@ -17,43 +17,41 @@ export const listFeaturesTool: ToolDefinition<typeof InputSchema> = {
|
|
|
17
17
|
Useful for understanding the strategic context and major milestones.`,
|
|
18
18
|
schema: InputSchema,
|
|
19
19
|
handler: async ({ projectId }, { supabase, userId }) => {
|
|
20
|
-
// 1.
|
|
21
|
-
const { data:
|
|
22
|
-
.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.single();
|
|
20
|
+
// 1. Verify project access and fetch context
|
|
21
|
+
const { data: projectRow, error: projectError } = await supabase
|
|
22
|
+
.rpc('get_project_context_secure', {
|
|
23
|
+
p_project_id: projectId,
|
|
24
|
+
p_user_id: userId
|
|
25
|
+
})
|
|
26
|
+
.single() as any;
|
|
27
27
|
|
|
28
|
-
if (projectError || !
|
|
29
|
-
throw new Error('Project not found or access denied');
|
|
28
|
+
if (projectError || !projectRow) {
|
|
29
|
+
throw new Error('Project details not found or access denied');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// 2. Primary Strategy: Fetch from 'project_features'
|
|
32
|
+
// 2. Primary Strategy: Fetch from 'project_features' via secure RPC
|
|
33
33
|
const { data: dbFeatures, error: dbError } = await supabase
|
|
34
|
-
.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.order('created_at', { ascending: false });
|
|
34
|
+
.rpc('get_project_features_secure', {
|
|
35
|
+
p_project_id: projectId,
|
|
36
|
+
p_user_id: userId
|
|
37
|
+
});
|
|
39
38
|
|
|
40
39
|
let featuresList: any[] = [];
|
|
41
40
|
let source = 'DB';
|
|
42
41
|
|
|
43
42
|
if (!dbError && dbFeatures && dbFeatures.length > 0) {
|
|
44
|
-
featuresList = dbFeatures.map(f => ({
|
|
43
|
+
featuresList = dbFeatures.map((f: any) => ({
|
|
45
44
|
...f,
|
|
46
|
-
title: f.name
|
|
45
|
+
title: f.name
|
|
47
46
|
}));
|
|
48
47
|
} else {
|
|
49
48
|
// 3. Fallback Strategy: Extract from 'functional_spec'
|
|
50
49
|
source = 'FALLBACK_SPEC';
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const spec = projectRow.functional_spec as any;
|
|
51
|
+
const features = spec?.featureList || spec?.features;
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
featuresList = spec.features.map((f: any) => ({
|
|
53
|
+
if (Array.isArray(features)) {
|
|
54
|
+
featuresList = features.map((f: any) => ({
|
|
57
55
|
id: 'legacy',
|
|
58
56
|
title: f.name || f.title,
|
|
59
57
|
description: f.description,
|
|
@@ -79,5 +77,3 @@ Useful for understanding the strategic context and major milestones.`,
|
|
|
79
77
|
};
|
|
80
78
|
|
|
81
79
|
registry.register(listFeaturesTool);
|
|
82
|
-
|
|
83
|
-
|
|
@@ -74,7 +74,12 @@ export async function listRoadmapTasks(
|
|
|
74
74
|
priority: t.priority,
|
|
75
75
|
status: t.status,
|
|
76
76
|
step_number: t.step_number,
|
|
77
|
-
prompt_content: t.prompt_content
|
|
77
|
+
prompt_content: t.prompt_content,
|
|
78
|
+
architectural_brief: t.architectural_brief,
|
|
79
|
+
context_summary: t.context_summary,
|
|
80
|
+
metadata: t.metadata,
|
|
81
|
+
checklist: t.checklist,
|
|
82
|
+
tags: t.tags
|
|
78
83
|
})),
|
|
79
84
|
formatted
|
|
80
85
|
};
|
|
@@ -48,8 +48,19 @@ export interface UpdateTaskStatusResponse {
|
|
|
48
48
|
*/
|
|
49
49
|
export async function getPendingTasks(
|
|
50
50
|
supabase: SupabaseClient,
|
|
51
|
+
userId: string,
|
|
51
52
|
projectId: string
|
|
52
53
|
): Promise<GetPendingTasksResponse> {
|
|
54
|
+
// 0. Verify project access
|
|
55
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
56
|
+
.rpc('check_project_access_secure', {
|
|
57
|
+
p_project_id: projectId,
|
|
58
|
+
p_user_id: userId
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (accessError || !hasAccess) {
|
|
62
|
+
throw new Error('Project not found or access denied');
|
|
63
|
+
}
|
|
53
64
|
|
|
54
65
|
// Fetch APPROVED tasks that are ready for execution
|
|
55
66
|
const { data: tasks, error } = await supabase
|
|
@@ -143,11 +154,22 @@ export async function getPendingTasks(
|
|
|
143
154
|
*/
|
|
144
155
|
export async function updateTaskStatus(
|
|
145
156
|
supabase: SupabaseClient,
|
|
157
|
+
userId: string,
|
|
146
158
|
projectId: string,
|
|
147
159
|
taskId: string,
|
|
148
160
|
newStatus: 'EXECUTING' | 'COMPLETED' | 'FAILED',
|
|
149
161
|
executionSummary?: string
|
|
150
162
|
): Promise<UpdateTaskStatusResponse> {
|
|
163
|
+
// 0. Verify project access
|
|
164
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
165
|
+
.rpc('check_project_access_secure', {
|
|
166
|
+
p_project_id: projectId,
|
|
167
|
+
p_user_id: userId
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (accessError || !hasAccess) {
|
|
171
|
+
throw new Error('Project not found or access denied');
|
|
172
|
+
}
|
|
151
173
|
|
|
152
174
|
// 1. Fetch the current task state
|
|
153
175
|
const { data: currentTask, error: fetchError } = await supabase
|
|
@@ -60,25 +60,21 @@ export async function saveToProjectBrain(
|
|
|
60
60
|
|
|
61
61
|
const fullContent = `# ${title}\n\n${content}`;
|
|
62
62
|
|
|
63
|
-
const { data, error } = await supabase
|
|
64
|
-
.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
source_type: 'chat_manual'
|
|
73
|
-
})
|
|
74
|
-
.select('id')
|
|
75
|
-
.single();
|
|
63
|
+
const { data: memoryId, error } = await supabase
|
|
64
|
+
.rpc('save_project_memory_secure', {
|
|
65
|
+
p_project_id: projectId,
|
|
66
|
+
p_user_id: userId,
|
|
67
|
+
p_content: fullContent,
|
|
68
|
+
p_category: category.toLowerCase(),
|
|
69
|
+
p_tags: tags || [],
|
|
70
|
+
p_importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5
|
|
71
|
+
});
|
|
76
72
|
|
|
77
73
|
if (error) throw new Error(`Failed to save memory: ${error.message}`);
|
|
78
74
|
|
|
79
75
|
return {
|
|
80
76
|
success: true,
|
|
81
|
-
memoryId:
|
|
77
|
+
memoryId: memoryId,
|
|
82
78
|
message: `✅ Saved [${category}] "${title}" to Project Brain.`
|
|
83
79
|
};
|
|
84
80
|
}
|
package/src/tools/query-brain.ts
CHANGED
|
@@ -75,55 +75,34 @@ export async function queryBrain(
|
|
|
75
75
|
limit: number = 8,
|
|
76
76
|
threshold: number = 0.5
|
|
77
77
|
): Promise<BrainQueryResponse> {
|
|
78
|
-
// First, verify project
|
|
79
|
-
const { data:
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (projectError || !project) {
|
|
78
|
+
// First, verify project access
|
|
79
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
80
|
+
.rpc('check_project_access_secure', {
|
|
81
|
+
p_project_id: projectId,
|
|
82
|
+
p_user_id: userId
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (accessError || !hasAccess) {
|
|
87
86
|
throw new Error('Project not found or access denied');
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
// Try semantic search first using the match_memories RPC
|
|
91
|
-
// This requires the embedding to be generated, so we'll try
|
|
92
|
-
// Generate embedding if possible for semantic search
|
|
93
90
|
const embedding = await generateQueryEmbedding(query);
|
|
94
91
|
let memories: MemoryRecord[] = [];
|
|
95
92
|
|
|
96
|
-
// Use the
|
|
93
|
+
// Use the secure search RPC
|
|
97
94
|
const { data: searchResults, error: searchError } = await supabase
|
|
98
|
-
.rpc('
|
|
95
|
+
.rpc('query_project_brain_secure', {
|
|
99
96
|
p_project_id: projectId,
|
|
97
|
+
p_user_id: userId,
|
|
100
98
|
p_query: query,
|
|
101
|
-
|
|
102
|
-
p_limit: limit,
|
|
103
|
-
p_similarity_threshold: threshold || 0.1
|
|
99
|
+
p_limit: limit
|
|
104
100
|
});
|
|
105
101
|
|
|
106
|
-
if (searchError) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.select('id, content, category, tags, importance, created_at')
|
|
111
|
-
.eq('project_id', projectId)
|
|
112
|
-
.eq('is_active', true)
|
|
113
|
-
.order('created_at', { ascending: false })
|
|
114
|
-
.limit(limit);
|
|
115
|
-
|
|
116
|
-
if (recentMemories) {
|
|
117
|
-
memories = recentMemories.map(m => ({
|
|
118
|
-
id: m.id,
|
|
119
|
-
content: m.content,
|
|
120
|
-
category: m.category || 'general',
|
|
121
|
-
tags: m.tags || [],
|
|
122
|
-
netVotes: m.importance || 0,
|
|
123
|
-
createdAt: m.created_at
|
|
124
|
-
}));
|
|
125
|
-
}
|
|
126
|
-
} else if (searchResults) {
|
|
102
|
+
if (searchError || !searchResults) {
|
|
103
|
+
console.error('Brain query failed:', searchError);
|
|
104
|
+
memories = [];
|
|
105
|
+
} else {
|
|
127
106
|
memories = searchResults.map((m: any) => ({
|
|
128
107
|
id: m.id,
|
|
129
108
|
content: m.content,
|
|
@@ -134,31 +113,39 @@ export async function queryBrain(
|
|
|
134
113
|
}));
|
|
135
114
|
}
|
|
136
115
|
|
|
137
|
-
// ---
|
|
138
|
-
// Simple fuzzy search until we vector embedding for features.
|
|
116
|
+
// --- FETCH RELEVANT FEATURES FROM SPEC ---
|
|
139
117
|
let relevantFeatures: any[] = [];
|
|
140
118
|
try {
|
|
141
|
-
const { data:
|
|
142
|
-
.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.
|
|
147
|
-
|
|
148
|
-
if (
|
|
119
|
+
const { data: projectRow } = await supabase
|
|
120
|
+
.rpc('get_project_context_secure', {
|
|
121
|
+
p_project_id: projectId,
|
|
122
|
+
p_user_id: userId
|
|
123
|
+
})
|
|
124
|
+
.single() as any;
|
|
125
|
+
|
|
126
|
+
if (projectRow?.functional_spec) {
|
|
127
|
+
const spec = typeof projectRow.functional_spec === 'string'
|
|
128
|
+
? JSON.parse(projectRow.functional_spec)
|
|
129
|
+
: projectRow.functional_spec;
|
|
130
|
+
|
|
131
|
+
const features = spec.featureList || spec.features || [];
|
|
132
|
+
relevantFeatures = features
|
|
133
|
+
.filter((f: any) => (f.name || f.title)?.toLowerCase().includes(query.toLowerCase()))
|
|
134
|
+
.slice(0, 3);
|
|
135
|
+
}
|
|
149
136
|
} catch (e) {
|
|
150
137
|
console.warn('Feature fetch failed in brain query', e);
|
|
151
138
|
}
|
|
152
139
|
|
|
153
140
|
// Format memories into a readable context block
|
|
154
|
-
const contextLines = memories.map((m) => {
|
|
141
|
+
const contextLines = memories.map((m: any) => {
|
|
155
142
|
const voteIndicator = m.netVotes && m.netVotes < 0 ? ` [⚠️ POORLY RATED: ${m.netVotes}]` : '';
|
|
156
143
|
const tagStr = m.tags && m.tags.length > 0 ? ` [${m.tags.join(', ')}]` : '';
|
|
157
144
|
const category = m.category ? m.category.toUpperCase() : 'GENERAL';
|
|
158
145
|
return `- [${category}]${tagStr}${voteIndicator}: ${m.content}`;
|
|
159
146
|
});
|
|
160
147
|
|
|
161
|
-
const searchType = embedding ? 'TRIPLE-HYBRID (Vector + FTS + Fuzzy)' : '
|
|
148
|
+
const searchType = embedding ? 'TRIPLE-HYBRID (Vector + FTS + Fuzzy)' : 'SECURE-GATEWAY (Fuzzy)';
|
|
162
149
|
|
|
163
150
|
let formatted = `=== PROJECT BRAIN: RELEVANT MEMORIES ===
|
|
164
151
|
Search Mode: ${searchType}
|
|
@@ -166,7 +153,7 @@ Query: "${query}"`;
|
|
|
166
153
|
|
|
167
154
|
if (relevantFeatures.length > 0) {
|
|
168
155
|
formatted += `\n\n=== RELATED FEATURES ===\n` +
|
|
169
|
-
relevantFeatures.map((f: any) => `- ${f.name} [${f.status}]`).join('\n');
|
|
156
|
+
relevantFeatures.map((f: any) => `- ${f.name || f.title} [${f.status || 'Active'}]`).join('\n');
|
|
170
157
|
}
|
|
171
158
|
|
|
172
159
|
formatted += `\n\nFound ${memories.length} relevant memories:\n\n${contextLines.join('\n')}\n\n==========================================`;
|
|
@@ -13,14 +13,15 @@ export async function queryProjectBrain(
|
|
|
13
13
|
const { projectId, query, limit = 5 } = input;
|
|
14
14
|
|
|
15
15
|
// Verify access
|
|
16
|
-
const { data:
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
17
|
+
.rpc('check_project_access_secure', {
|
|
18
|
+
p_project_id: projectId,
|
|
19
|
+
p_user_id: userId
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (accessError || !hasAccess) {
|
|
23
|
+
throw new Error('Access denied or project not found');
|
|
24
|
+
}
|
|
24
25
|
|
|
25
26
|
// Simple keyword search
|
|
26
27
|
const terms = query.toLowerCase().split(/\s+/).filter(t => t.length > 2);
|
|
@@ -110,15 +110,14 @@ export async function runArchitectureAudit(
|
|
|
110
110
|
filePath: string,
|
|
111
111
|
content: string
|
|
112
112
|
): Promise<ArchitectureAuditResponse> {
|
|
113
|
-
// First, verify project
|
|
114
|
-
const { data:
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (projectError || !project) {
|
|
113
|
+
// First, verify project access
|
|
114
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
115
|
+
.rpc('check_project_access_secure', {
|
|
116
|
+
p_project_id: projectId,
|
|
117
|
+
p_user_id: userId
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (accessError || !hasAccess) {
|
|
122
121
|
throw new Error('Project not found or access denied');
|
|
123
122
|
}
|
|
124
123
|
|
|
@@ -44,18 +44,24 @@ export async function saveDecision(
|
|
|
44
44
|
category: string = 'decision',
|
|
45
45
|
tags: string[] = []
|
|
46
46
|
): Promise<SaveDecisionResponse> {
|
|
47
|
-
//
|
|
48
|
-
const { data:
|
|
49
|
-
.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.single();
|
|
47
|
+
// 1. Verify project access
|
|
48
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
49
|
+
.rpc('check_project_access_secure', {
|
|
50
|
+
p_project_id: projectId,
|
|
51
|
+
p_user_id: userId
|
|
52
|
+
});
|
|
54
53
|
|
|
55
|
-
if (
|
|
54
|
+
if (accessError || !hasAccess) {
|
|
56
55
|
throw new Error('Project not found or access denied');
|
|
57
56
|
}
|
|
58
57
|
|
|
58
|
+
// 2. Fetch project name for the response message
|
|
59
|
+
const { data: project } = await supabase
|
|
60
|
+
.from('projects')
|
|
61
|
+
.select('name')
|
|
62
|
+
.eq('id', projectId)
|
|
63
|
+
.single();
|
|
64
|
+
|
|
59
65
|
// Build the full content with title, decision, and rationale
|
|
60
66
|
const contentParts = [`# ${title}`, '', decision];
|
|
61
67
|
if (rationale) {
|
|
@@ -98,6 +104,6 @@ export async function saveDecision(
|
|
|
98
104
|
return {
|
|
99
105
|
success: true,
|
|
100
106
|
memoryId: memory.id,
|
|
101
|
-
message: `✅ Decision "${title}" saved to project "${project
|
|
107
|
+
message: `✅ Decision "${title}" saved to project "${project?.name || projectId}" with importance 9/10`
|
|
102
108
|
};
|
|
103
109
|
}
|
|
@@ -14,7 +14,7 @@ registry.register({
|
|
|
14
14
|
description: `Sven's Tool: Security Shield. Audits the database to ensure Row Level Security (RLS) is enforced.`,
|
|
15
15
|
schema: AuditRlsStatusInputSchema,
|
|
16
16
|
handler: async (args, context) => {
|
|
17
|
-
const result = await auditRlsStatus(context.supabase, args);
|
|
17
|
+
const result = await auditRlsStatus(context.supabase, context.userId, args);
|
|
18
18
|
return { content: [{ type: 'text', text: result.summary || 'No summary available' }] };
|
|
19
19
|
}
|
|
20
20
|
});
|
|
@@ -24,7 +24,7 @@ registry.register({
|
|
|
24
24
|
description: `Frank's Tool: Security Oracle. Performs a diagnostic security audit against the Fortress Matrix.`,
|
|
25
25
|
schema: AuditSecurityIntegrityInputSchema,
|
|
26
26
|
handler: async (args, context) => {
|
|
27
|
-
const result = await auditSecurityIntegrity(context.supabase, args);
|
|
27
|
+
const result = await auditSecurityIntegrity(context.supabase, context.userId, args);
|
|
28
28
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
29
29
|
}
|
|
30
30
|
});
|
|
@@ -35,8 +35,20 @@ registry.register({
|
|
|
35
35
|
*/
|
|
36
36
|
export async function auditRlsStatus(
|
|
37
37
|
supabase: SupabaseClient,
|
|
38
|
+
userId: string,
|
|
38
39
|
input: AuditRlsStatusInput
|
|
39
40
|
) {
|
|
41
|
+
// 0. Verify project access
|
|
42
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
43
|
+
.rpc('check_project_access_secure', {
|
|
44
|
+
p_project_id: input.projectId,
|
|
45
|
+
p_user_id: userId
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (accessError || !hasAccess) {
|
|
49
|
+
throw new Error('Project not found or access denied');
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
try {
|
|
41
53
|
const { data, error } = await supabase.rpc('execute_sql', {
|
|
42
54
|
query: `
|
|
@@ -97,8 +109,20 @@ export async function auditRlsStatus(
|
|
|
97
109
|
*/
|
|
98
110
|
export async function auditSecurityIntegrity(
|
|
99
111
|
supabase: SupabaseClient,
|
|
112
|
+
userId: string,
|
|
100
113
|
input: { projectId: string, filePath: string, content: string }
|
|
101
114
|
) {
|
|
115
|
+
// 0. Verify project access
|
|
116
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
117
|
+
.rpc('check_project_access_secure', {
|
|
118
|
+
p_project_id: input.projectId,
|
|
119
|
+
p_user_id: userId
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (accessError || !hasAccess) {
|
|
123
|
+
throw new Error('Project not found or access denied');
|
|
124
|
+
}
|
|
125
|
+
|
|
102
126
|
const violations: any[] = [];
|
|
103
127
|
const { content, filePath } = input;
|
|
104
128
|
|
package/src/tools/submit-idea.ts
CHANGED
|
@@ -42,18 +42,24 @@ export async function submitIdea(
|
|
|
42
42
|
category: string = 'feature',
|
|
43
43
|
tags: string[] = []
|
|
44
44
|
): Promise<SubmitIdeaResponse> {
|
|
45
|
-
//
|
|
46
|
-
const { data:
|
|
47
|
-
.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.single();
|
|
45
|
+
// 1. Verify project access
|
|
46
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
47
|
+
.rpc('check_project_access_secure', {
|
|
48
|
+
p_project_id: projectId,
|
|
49
|
+
p_user_id: userId
|
|
50
|
+
});
|
|
52
51
|
|
|
53
|
-
if (
|
|
52
|
+
if (accessError || !hasAccess) {
|
|
54
53
|
throw new Error('Project not found or access denied');
|
|
55
54
|
}
|
|
56
55
|
|
|
56
|
+
// 2. Fetch project name for the response message
|
|
57
|
+
const { data: project } = await supabase
|
|
58
|
+
.from('projects')
|
|
59
|
+
.select('name')
|
|
60
|
+
.eq('id', projectId)
|
|
61
|
+
.single();
|
|
62
|
+
|
|
57
63
|
// Insert the idea into saved_ideas
|
|
58
64
|
const { data: idea, error: insertError } = await supabase
|
|
59
65
|
.from('saved_ideas')
|
|
@@ -86,6 +92,6 @@ export async function submitIdea(
|
|
|
86
92
|
return {
|
|
87
93
|
success: true,
|
|
88
94
|
ideaId: idea.id,
|
|
89
|
-
message: `💡 Idea "${title}" submitted to Idea Lab for project "${project
|
|
95
|
+
message: `💡 Idea "${title}" submitted to Idea Lab for project "${project?.name || projectId}". Status: Draft (awaiting review)`
|
|
90
96
|
};
|
|
91
97
|
}
|
|
@@ -22,7 +22,7 @@ registry.register({
|
|
|
22
22
|
based on project context and user settings.`,
|
|
23
23
|
schema: GenerateCursorRulesInputSchema,
|
|
24
24
|
handler: async (args, context) => {
|
|
25
|
-
const result = await syncIdeRules(context.supabase, args.projectId);
|
|
25
|
+
const result = await syncIdeRules(context.supabase, context.userId, args.projectId);
|
|
26
26
|
|
|
27
27
|
// Format response: Main file content + information about modular files
|
|
28
28
|
let responseText = `FileName: ${result.fileName}\n\nContent:\n${result.content}`;
|
|
@@ -43,14 +43,27 @@ based on project context and user settings.`,
|
|
|
43
43
|
* Uses the centralized rules-engine for consistent generation across all consumers.
|
|
44
44
|
*
|
|
45
45
|
* @param supabase - Supabase client instance
|
|
46
|
+
* @param userId - The user ID for access verification
|
|
46
47
|
* @param projectId - The project ID to generate rules for
|
|
47
48
|
* @returns Object with fileName, content, and the new modular files[] array
|
|
48
49
|
*/
|
|
49
50
|
export async function syncIdeRules(
|
|
50
51
|
supabase: SupabaseClient,
|
|
52
|
+
userId: string,
|
|
51
53
|
projectId: string
|
|
52
54
|
): Promise<{ fileName: string; content: string; files: RuleFile[] }> {
|
|
53
|
-
// 1.
|
|
55
|
+
// 1. Verify project access
|
|
56
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
57
|
+
.rpc('check_project_access_secure', {
|
|
58
|
+
p_project_id: projectId,
|
|
59
|
+
p_user_id: userId
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (accessError || !hasAccess) {
|
|
63
|
+
throw new Error('Project not found or access denied');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 2. Fetch Project & Preferred IDE
|
|
54
67
|
const { data: project, error: projectError } = await supabase
|
|
55
68
|
.from('projects')
|
|
56
69
|
.select('*')
|
|
@@ -58,7 +71,7 @@ export async function syncIdeRules(
|
|
|
58
71
|
.single();
|
|
59
72
|
|
|
60
73
|
if (projectError || !project) {
|
|
61
|
-
throw new Error(`Project ${projectId} not found.`);
|
|
74
|
+
throw new Error(`Project ${projectId} details not found.`);
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
// 2. Determine IDE (Preference -> Fallback)
|
|
@@ -65,14 +65,13 @@ export async function refineLogic(
|
|
|
65
65
|
const traceId = uuidv4();
|
|
66
66
|
|
|
67
67
|
// Verify project access
|
|
68
|
-
const { data:
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.single();
|
|
68
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
69
|
+
.rpc('check_project_access_secure', {
|
|
70
|
+
p_project_id: projectId,
|
|
71
|
+
p_user_id: userId
|
|
72
|
+
});
|
|
74
73
|
|
|
75
|
-
if (
|
|
74
|
+
if (accessError || !hasAccess) {
|
|
76
75
|
throw new Error(`Project access denied or not found: ${projectId}`);
|
|
77
76
|
}
|
|
78
77
|
|
|
@@ -40,18 +40,24 @@ export async function updateRoadmap(
|
|
|
40
40
|
chunkId?: string,
|
|
41
41
|
title?: string
|
|
42
42
|
): Promise<UpdateRoadmapResponse> {
|
|
43
|
-
//
|
|
44
|
-
const { data:
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.single();
|
|
43
|
+
// 1. Verify project access
|
|
44
|
+
const { data: hasAccess, error: accessError } = await supabase
|
|
45
|
+
.rpc('check_project_access_secure', {
|
|
46
|
+
p_project_id: projectId,
|
|
47
|
+
p_user_id: userId
|
|
48
|
+
});
|
|
50
49
|
|
|
51
|
-
if (
|
|
50
|
+
if (accessError || !hasAccess) {
|
|
52
51
|
throw new Error('Project not found or access denied');
|
|
53
52
|
}
|
|
54
53
|
|
|
54
|
+
// 2. Fetch project details (for response message if needed)
|
|
55
|
+
const { data: project } = await supabase
|
|
56
|
+
.from('projects')
|
|
57
|
+
.select('name')
|
|
58
|
+
.eq('id', projectId)
|
|
59
|
+
.single();
|
|
60
|
+
|
|
55
61
|
// Find the roadmap chunk
|
|
56
62
|
let targetChunk: { id: string; title: string; status: string } | null = null;
|
|
57
63
|
|