@rigstate/mcp 0.7.7 → 0.7.9
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 +138 -147
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/tools/get-project-context.ts +50 -64
- package/src/tools/list-features.ts +34 -32
- package/src/tools/planning-tools.ts +42 -3
- package/src/tools/query-brain.ts +51 -113
package/package.json
CHANGED
|
@@ -50,52 +50,54 @@ export async function getProjectContext(
|
|
|
50
50
|
userId: string,
|
|
51
51
|
projectId: string
|
|
52
52
|
): Promise<ProjectContextResponse> {
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
name: string;
|
|
57
|
-
description: string;
|
|
58
|
-
project_type: string;
|
|
59
|
-
created_at: string;
|
|
60
|
-
last_indexed_at: string;
|
|
61
|
-
detected_stack: any;
|
|
62
|
-
repository_tree: any;
|
|
63
|
-
architectural_dna: any;
|
|
64
|
-
functional_spec: any;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Fetch project securely via RPC (bypasses RLS with strict checks)
|
|
68
|
-
const { data: rawData, error: projectError } = await supabase
|
|
69
|
-
.rpc('get_project_context_secure', {
|
|
70
|
-
p_project_id: projectId,
|
|
71
|
-
p_user_id: userId
|
|
72
|
-
})
|
|
73
|
-
.single();
|
|
53
|
+
// 1. Try secure RPC first
|
|
54
|
+
let projectRow: any = null;
|
|
55
|
+
let projectError: any = null;
|
|
74
56
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
57
|
+
try {
|
|
58
|
+
const { data, error } = await supabase
|
|
59
|
+
.rpc('get_project_context_secure', {
|
|
60
|
+
p_project_id: projectId,
|
|
61
|
+
p_user_id: userId
|
|
62
|
+
})
|
|
63
|
+
.single();
|
|
64
|
+
|
|
65
|
+
if (data && !error) {
|
|
66
|
+
projectRow = data;
|
|
67
|
+
} else {
|
|
68
|
+
projectError = error;
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
projectError = e;
|
|
80
72
|
}
|
|
81
73
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
// 2. Fallback to direct table query if RPC failed (Policies were updated to allow members)
|
|
75
|
+
if (!projectRow) {
|
|
76
|
+
console.error(`RPC get_project_context_secure failed for ${projectId}, falling back to direct query. Error:`, projectError);
|
|
77
|
+
const { data, error } = await supabase
|
|
78
|
+
.from('projects')
|
|
79
|
+
.select(`
|
|
80
|
+
id, name, description, project_type, created_at, last_indexed_at, detected_stack, repository_tree, functional_spec,
|
|
81
|
+
architectural_dna(stack_definition)
|
|
82
|
+
`)
|
|
83
|
+
.eq('id', projectId)
|
|
84
|
+
.single();
|
|
85
|
+
|
|
86
|
+
if (data) {
|
|
87
|
+
projectRow = {
|
|
88
|
+
...data,
|
|
89
|
+
architectural_dna: Array.isArray(data.architectural_dna) ? data.architectural_dna[0] : data.architectural_dna
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
console.error('Project fetch failed completely:', error);
|
|
93
|
+
throw new Error(`Project ${projectId} not found or access denied. (User: ${userId})`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
const stackDef =
|
|
97
|
+
const project = projectRow;
|
|
98
|
+
const stackDef = projectRow.architectural_dna?.stack_definition;
|
|
97
99
|
|
|
98
|
-
// Continuity Loop: Fetch Active & Next Tasks
|
|
100
|
+
// 3. Continuity Loop: Fetch Active & Next Tasks
|
|
99
101
|
const { data: allChunks } = await supabase
|
|
100
102
|
.rpc('get_roadmap_chunks_secure', {
|
|
101
103
|
p_project_id: projectId,
|
|
@@ -103,14 +105,12 @@ export async function getProjectContext(
|
|
|
103
105
|
});
|
|
104
106
|
|
|
105
107
|
const tasks = (allChunks || []) as any[];
|
|
106
|
-
|
|
107
|
-
// In-memory filtering to match original logic
|
|
108
108
|
const activeTask = tasks.find(t => ['IN_PROGRESS', 'ACTIVE'].includes(t.status));
|
|
109
109
|
const nextTask = tasks
|
|
110
110
|
.filter(t => ['PENDING', 'LOCKED'].includes(t.status))
|
|
111
111
|
.sort((a, b) => a.step_number - b.step_number)[0];
|
|
112
112
|
|
|
113
|
-
// Fetch Digest Data
|
|
113
|
+
// 4. Fetch Digest Data
|
|
114
114
|
const { data: agentTasks } = await supabase
|
|
115
115
|
.rpc('get_agent_bridge_secure', {
|
|
116
116
|
p_project_id: projectId,
|
|
@@ -122,7 +122,7 @@ export async function getProjectContext(
|
|
|
122
122
|
.sort((a, b) => new Date(b.updated_at || b.created_at).getTime() - new Date(a.updated_at || a.created_at).getTime())
|
|
123
123
|
.slice(0, 2);
|
|
124
124
|
|
|
125
|
-
//
|
|
125
|
+
// 5. Build tech stack info
|
|
126
126
|
const techStack: TechStackInfo = {
|
|
127
127
|
framework: null,
|
|
128
128
|
orm: null,
|
|
@@ -132,31 +132,21 @@ export async function getProjectContext(
|
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
if (project.detected_stack) {
|
|
135
|
-
const stack = project.detected_stack as
|
|
136
|
-
dependencies?: Record<string, string>;
|
|
137
|
-
devDependencies?: Record<string, string>;
|
|
138
|
-
};
|
|
135
|
+
const stack = project.detected_stack as any;
|
|
139
136
|
const allDeps = { ...stack.dependencies, ...stack.devDependencies };
|
|
140
137
|
|
|
141
138
|
for (const [name, version] of Object.entries(allDeps)) {
|
|
142
139
|
const cleanVersion = (version as string).replace(/[\^~]/g, '');
|
|
143
|
-
|
|
144
|
-
// Categorize
|
|
145
140
|
if (name === 'next' || name === 'react' || name === 'vue' || name === 'angular' || name === 'svelte') {
|
|
146
|
-
if (!techStack.framework) {
|
|
147
|
-
techStack.framework = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
148
|
-
}
|
|
141
|
+
if (!techStack.framework) techStack.framework = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
149
142
|
} else if (name.includes('prisma') || name.includes('drizzle') || name.includes('typeorm')) {
|
|
150
|
-
if (!techStack.orm) {
|
|
151
|
-
techStack.orm = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
152
|
-
}
|
|
143
|
+
if (!techStack.orm) techStack.orm = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
153
144
|
} else if (KEY_LIBS[name]) {
|
|
154
145
|
techStack.keyLibraries.push(`${KEY_LIBS[name]} ${cleanVersion}`);
|
|
155
146
|
}
|
|
156
147
|
}
|
|
157
148
|
}
|
|
158
149
|
|
|
159
|
-
// Extract top folders from repository tree
|
|
160
150
|
if (project.repository_tree && Array.isArray(project.repository_tree)) {
|
|
161
151
|
techStack.topFolders = [...new Set(
|
|
162
152
|
project.repository_tree
|
|
@@ -190,18 +180,14 @@ export async function getProjectContext(
|
|
|
190
180
|
summary
|
|
191
181
|
};
|
|
192
182
|
|
|
193
|
-
//
|
|
183
|
+
// Inject Global Context
|
|
194
184
|
try {
|
|
195
185
|
const curatorContext = await injectGlobalContext(supabase, userId, {
|
|
196
186
|
frameworks: techStack.framework ? [techStack.framework] : [],
|
|
197
187
|
libraries: techStack.keyLibraries
|
|
198
188
|
});
|
|
199
|
-
|
|
200
|
-
if (curatorContext) {
|
|
201
|
-
response.summary += `\n\n=== CURATOR INTELLIGENCE ===${curatorContext}`;
|
|
202
|
-
}
|
|
189
|
+
if (curatorContext) response.summary += `\n\n=== CURATOR INTELLIGENCE ===${curatorContext}`;
|
|
203
190
|
} catch (e: any) {
|
|
204
|
-
console.error('Failed to inject global context:', e);
|
|
205
191
|
response.summary += `\n\n(Curator Context Unavailable: ${e.message})`;
|
|
206
192
|
}
|
|
207
193
|
|
|
@@ -17,39 +17,46 @@ 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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.
|
|
20
|
+
// 1. Fetch project and features
|
|
21
|
+
let projectRow: any = null;
|
|
22
|
+
let dbFeatures: any[] = [];
|
|
23
|
+
let source = 'DB';
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// 1. Try secure gateway for project context
|
|
27
|
+
const { data, error } = await supabase
|
|
28
|
+
.rpc('get_project_context_secure', { p_project_id: projectId, p_user_id: userId })
|
|
29
|
+
.single();
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
if (error || !data) throw error || new Error('Project not found');
|
|
32
|
+
projectRow = data;
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error('[list_features] Secure Project RPC failed:', e);
|
|
35
|
+
// Fallback: Direct table query (Requires proper RLS)
|
|
36
|
+
const { data } = await supabase.from('projects').select('id, functional_spec').eq('id', projectId).single();
|
|
37
|
+
projectRow = data;
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
const { data: dbFeatures, error: dbError } = await supabase
|
|
34
|
-
.rpc('get_project_features_secure', {
|
|
35
|
-
p_project_id: projectId,
|
|
36
|
-
p_user_id: userId
|
|
37
|
-
});
|
|
40
|
+
if (!projectRow) throw new Error(`Project ${projectId} not found or access denied.`);
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
try {
|
|
43
|
+
// Try secure gateway for features
|
|
44
|
+
const { data } = await supabase.rpc('get_project_features_secure', { p_project_id: projectId, p_user_id: userId });
|
|
45
|
+
dbFeatures = data || [];
|
|
46
|
+
} catch (e) {
|
|
47
|
+
// Fallback to direct table
|
|
48
|
+
const { data } = await supabase.from('project_features').select('id, name, description, status').eq('project_id', projectId);
|
|
49
|
+
dbFeatures = data || [];
|
|
50
|
+
}
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
title: f.name
|
|
46
|
-
}));
|
|
52
|
+
let featuresList: any[] = [];
|
|
53
|
+
if (dbFeatures && dbFeatures.length > 0) {
|
|
54
|
+
featuresList = dbFeatures.map((f: any) => ({ ...f, title: f.name }));
|
|
47
55
|
} else {
|
|
48
|
-
//
|
|
56
|
+
// Fallback to spec
|
|
49
57
|
source = 'FALLBACK_SPEC';
|
|
50
58
|
const spec = projectRow.functional_spec as any;
|
|
51
59
|
const features = spec?.featureList || spec?.features;
|
|
52
|
-
|
|
53
60
|
if (Array.isArray(features)) {
|
|
54
61
|
featuresList = features.map((f: any) => ({
|
|
55
62
|
id: 'legacy',
|
|
@@ -60,19 +67,14 @@ Useful for understanding the strategic context and major milestones.`,
|
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
// 4. Format Response
|
|
64
70
|
if (featuresList.length === 0) {
|
|
65
|
-
return { content: [{ type: 'text', text: 'No active features found
|
|
71
|
+
return { content: [{ type: 'text', text: 'No active features found.' }] };
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
const formatted = `=== PROJECT FEATURES (Source: ${source}) ===\n` +
|
|
69
|
-
featuresList.map(f => {
|
|
70
|
-
return `- ${f.title} [${f.status}]`;
|
|
71
|
-
}).join('\n');
|
|
75
|
+
featuresList.map(f => `- ${f.title} [${f.status}]`).join('\n');
|
|
72
76
|
|
|
73
|
-
return {
|
|
74
|
-
content: [{ type: 'text', text: formatted }]
|
|
75
|
-
};
|
|
77
|
+
return { content: [{ type: 'text', text: formatted }] };
|
|
76
78
|
}
|
|
77
79
|
};
|
|
78
80
|
|
|
@@ -60,8 +60,12 @@ export async function saveToProjectBrain(
|
|
|
60
60
|
|
|
61
61
|
const fullContent = `# ${title}\n\n${content}`;
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
let memoryId: string | null = null;
|
|
64
|
+
let saveError: any = null;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// 1. Try secure RPC
|
|
68
|
+
const { data, error } = await supabase.rpc('save_project_memory_secure', {
|
|
65
69
|
p_project_id: projectId,
|
|
66
70
|
p_user_id: userId,
|
|
67
71
|
p_content: fullContent,
|
|
@@ -70,7 +74,42 @@ export async function saveToProjectBrain(
|
|
|
70
74
|
p_importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5
|
|
71
75
|
});
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
if (error) {
|
|
78
|
+
console.error('[save_to_project_brain] RPC Error:', error);
|
|
79
|
+
saveError = error;
|
|
80
|
+
} else if (data) {
|
|
81
|
+
memoryId = data;
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error('[save_to_project_brain] RPC Exception:', e);
|
|
85
|
+
saveError = e;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 2. Fallback to direct insert (Requires proper RLS)
|
|
89
|
+
if (!memoryId) {
|
|
90
|
+
console.warn('[save_to_project_brain] Falling back to direct insert. RPC Error/Response was:', saveError);
|
|
91
|
+
const { data, error } = await supabase
|
|
92
|
+
.from('project_memories')
|
|
93
|
+
.insert({
|
|
94
|
+
project_id: projectId,
|
|
95
|
+
content: fullContent,
|
|
96
|
+
category: category.toLowerCase(),
|
|
97
|
+
tags: tags || [],
|
|
98
|
+
importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5,
|
|
99
|
+
is_active: true,
|
|
100
|
+
source_type: 'chat_manual'
|
|
101
|
+
})
|
|
102
|
+
.select('id')
|
|
103
|
+
.single();
|
|
104
|
+
|
|
105
|
+
if (data) {
|
|
106
|
+
memoryId = (data as any).id;
|
|
107
|
+
} else {
|
|
108
|
+
console.error('[save_to_project_brain] Direct insert failed:', error);
|
|
109
|
+
const detail = error?.message || (saveError as any)?.message || 'Unknown error';
|
|
110
|
+
throw new Error(`Failed to save memory: ${detail}. (Note: secure RPC failed first)`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
74
113
|
|
|
75
114
|
return {
|
|
76
115
|
success: true,
|
package/src/tools/query-brain.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Tool: query_brain
|
|
3
3
|
*
|
|
4
4
|
* Takes a natural language query and performs semantic search
|
|
5
|
-
* against the project's memories (RAG)
|
|
6
|
-
* architecture rules, decisions, and constraints.
|
|
5
|
+
* against the project's memories (RAG).
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
@@ -11,116 +10,76 @@ import type { BrainQueryResponse, MemoryRecord } from '../lib/types.js';
|
|
|
11
10
|
import { registry } from '../lib/tool-registry.js';
|
|
12
11
|
import { QueryBrainInputSchema } from '../lib/schemas.js';
|
|
13
12
|
|
|
14
|
-
// ============================================
|
|
15
|
-
// Tool Registration
|
|
16
|
-
// ============================================
|
|
17
|
-
|
|
18
13
|
registry.register({
|
|
19
14
|
name: 'query_brain',
|
|
20
|
-
description: `Takes a natural language query and performs semantic search
|
|
21
|
-
against the project's memories (RAG), returning relevant
|
|
22
|
-
architecture rules, decisions, and constraints.`,
|
|
15
|
+
description: `Takes a natural language query and performs semantic search against the project's memories (RAG).`,
|
|
23
16
|
schema: QueryBrainInputSchema,
|
|
24
17
|
handler: async (args, context) => {
|
|
25
|
-
const result = await queryBrain(
|
|
26
|
-
context.supabase,
|
|
27
|
-
context.userId,
|
|
28
|
-
args.projectId,
|
|
29
|
-
args.query,
|
|
30
|
-
args.limit,
|
|
31
|
-
args.threshold
|
|
32
|
-
);
|
|
18
|
+
const result = await queryBrain(context.supabase, context.userId, args.projectId, args.query, args.limit);
|
|
33
19
|
return { content: [{ type: 'text', text: result.formatted }] };
|
|
34
20
|
}
|
|
35
21
|
});
|
|
36
22
|
|
|
37
|
-
// Generate embedding using the Rigstate Intelligence API (Proxy)
|
|
38
|
-
async function generateQueryEmbedding(query: string): Promise<number[] | null> {
|
|
39
|
-
const apiKey = process.env.RIGSTATE_API_KEY;
|
|
40
|
-
const apiUrl = process.env.RIGSTATE_API_URL || 'http://localhost:3000/api/v1';
|
|
41
|
-
|
|
42
|
-
if (!apiKey) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const response = await fetch(`${apiUrl}/intelligence/embed`, {
|
|
48
|
-
method: 'POST',
|
|
49
|
-
headers: {
|
|
50
|
-
'Content-Type': 'application/json',
|
|
51
|
-
'Authorization': `Bearer ${apiKey}`
|
|
52
|
-
},
|
|
53
|
-
body: JSON.stringify({ text: query })
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
if (!response.ok) {
|
|
57
|
-
const errorText = await response.text();
|
|
58
|
-
console.error(`Embedding API error (${response.status}):`, errorText);
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const result = await response.json() as any;
|
|
63
|
-
return result.data?.embedding || null;
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error('Failed to generate embedding via Proxy:', error);
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
23
|
export async function queryBrain(
|
|
71
24
|
supabase: SupabaseClient,
|
|
72
25
|
userId: string,
|
|
73
26
|
projectId: string,
|
|
74
27
|
query: string,
|
|
75
|
-
limit: number = 8
|
|
76
|
-
threshold: number = 0.5
|
|
28
|
+
limit: number = 8
|
|
77
29
|
): Promise<BrainQueryResponse> {
|
|
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) {
|
|
86
|
-
throw new Error('Project not found or access denied');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Try semantic search first using the match_memories RPC
|
|
90
|
-
const embedding = await generateQueryEmbedding(query);
|
|
91
30
|
let memories: MemoryRecord[] = [];
|
|
31
|
+
let searchType = 'SECURE-GATEWAY';
|
|
92
32
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.rpc('query_project_brain_secure', {
|
|
33
|
+
try {
|
|
34
|
+
// Try secure search RPC
|
|
35
|
+
const { data, error } = await supabase.rpc('query_project_brain_secure', {
|
|
96
36
|
p_project_id: projectId,
|
|
97
37
|
p_user_id: userId,
|
|
98
38
|
p_query: query,
|
|
99
39
|
p_limit: limit
|
|
100
40
|
});
|
|
101
41
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
42
|
+
if (!error && data) {
|
|
43
|
+
memories = data.map((m: any) => ({
|
|
44
|
+
id: m.id,
|
|
45
|
+
content: m.content,
|
|
46
|
+
category: m.category,
|
|
47
|
+
tags: m.tags,
|
|
48
|
+
netVotes: m.importance,
|
|
49
|
+
createdAt: m.created_at
|
|
50
|
+
}));
|
|
51
|
+
} else {
|
|
52
|
+
throw error || new Error('No data');
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
// Fallback to direct table query (Policies allow members)
|
|
56
|
+
searchType = 'DIRECT-FALLBACK';
|
|
57
|
+
const { data } = await supabase
|
|
58
|
+
.from('project_memories')
|
|
59
|
+
.select('id, content, category, tags, importance, created_at')
|
|
60
|
+
.eq('project_id', projectId)
|
|
61
|
+
.eq('is_active', true)
|
|
62
|
+
.or(`content.ilike.%${query}%,category.ilike.%${query}%`)
|
|
63
|
+
.order('created_at', { ascending: false })
|
|
64
|
+
.limit(limit);
|
|
65
|
+
|
|
66
|
+
if (data) {
|
|
67
|
+
memories = data.map((m: any) => ({
|
|
68
|
+
id: m.id,
|
|
69
|
+
content: m.content,
|
|
70
|
+
category: m.category,
|
|
71
|
+
tags: m.tags,
|
|
72
|
+
netVotes: m.importance,
|
|
73
|
+
createdAt: m.created_at
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
114
76
|
}
|
|
115
77
|
|
|
116
|
-
//
|
|
78
|
+
// 3. Related Features from Spec (Use Secure Gateway)
|
|
117
79
|
let relevantFeatures: any[] = [];
|
|
118
80
|
try {
|
|
119
81
|
const { data: projectRow } = await supabase
|
|
120
|
-
.rpc('get_project_context_secure', {
|
|
121
|
-
p_project_id: projectId,
|
|
122
|
-
p_user_id: userId
|
|
123
|
-
})
|
|
82
|
+
.rpc('get_project_context_secure', { p_project_id: projectId, p_user_id: userId })
|
|
124
83
|
.single() as any;
|
|
125
84
|
|
|
126
85
|
if (projectRow?.functional_spec) {
|
|
@@ -134,40 +93,19 @@ export async function queryBrain(
|
|
|
134
93
|
.slice(0, 3);
|
|
135
94
|
}
|
|
136
95
|
} catch (e) {
|
|
137
|
-
console.
|
|
96
|
+
console.error('[query_brain] Failed to fetch project spec for features:', e);
|
|
138
97
|
}
|
|
139
98
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const voteIndicator = m.netVotes && m.netVotes < 0 ? ` [⚠️ POORLY RATED: ${m.netVotes}]` : '';
|
|
143
|
-
const tagStr = m.tags && m.tags.length > 0 ? ` [${m.tags.join(', ')}]` : '';
|
|
144
|
-
const category = m.category ? m.category.toUpperCase() : 'GENERAL';
|
|
145
|
-
return `- [${category}]${tagStr}${voteIndicator}: ${m.content}`;
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const searchType = embedding ? 'TRIPLE-HYBRID (Vector + FTS + Fuzzy)' : 'SECURE-GATEWAY (Fuzzy)';
|
|
149
|
-
|
|
150
|
-
let formatted = `=== PROJECT BRAIN: RELEVANT MEMORIES ===
|
|
151
|
-
Search Mode: ${searchType}
|
|
152
|
-
Query: "${query}"`;
|
|
153
|
-
|
|
99
|
+
const contextLines = memories.map((m: any) => `- [${(m.category || 'GENERAL').toUpperCase()}]: ${m.content}`);
|
|
100
|
+
let formatted = `=== PROJECT BRAIN: RELEVANT MEMORIES ===\nSearch Mode: ${searchType}\nQuery: "${query}"`;
|
|
154
101
|
if (relevantFeatures.length > 0) {
|
|
155
|
-
formatted += `\n\n=== RELATED FEATURES ===\n` +
|
|
156
|
-
relevantFeatures.map((f: any) => `- ${f.name || f.title} [${f.status || 'Active'}]`).join('\n');
|
|
102
|
+
formatted += `\n\n=== RELATED FEATURES ===\n` + relevantFeatures.map((f: any) => `- ${f.name || f.title} [${f.status || 'Active'}]`).join('\n');
|
|
157
103
|
}
|
|
158
|
-
|
|
159
|
-
formatted += `\n\nFound ${memories.length} relevant memories:\n\n${contextLines.join('\n')}\n\n==========================================`;
|
|
104
|
+
formatted += `\n\nFound ${memories.length} relevant memories:\n\n${contextLines.join('\n')}`;
|
|
160
105
|
|
|
161
106
|
if (memories.length === 0 && relevantFeatures.length === 0) {
|
|
162
|
-
formatted = `=== PROJECT BRAIN
|
|
163
|
-
Query: "${query}"
|
|
164
|
-
No relevant memories or features found.
|
|
165
|
-
=======================`;
|
|
107
|
+
formatted = `=== PROJECT BRAIN ===\nNo relevant info found for "${query}".`;
|
|
166
108
|
}
|
|
167
109
|
|
|
168
|
-
return {
|
|
169
|
-
query,
|
|
170
|
-
memories,
|
|
171
|
-
formatted
|
|
172
|
-
};
|
|
110
|
+
return { query, memories, formatted };
|
|
173
111
|
}
|