@rigstate/mcp 0.7.6 → 0.7.8
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 +165 -175
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/project-context-utils.ts +32 -23
- package/src/tools/get-project-context.ts +52 -66
- package/src/tools/list-features.ts +33 -48
- package/src/tools/planning-tools.ts +46 -15
- package/src/tools/query-brain.ts +48 -130
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TechStackInfo } from '../lib/types.js';
|
|
2
2
|
|
|
3
3
|
export async function buildProjectSummary(
|
|
4
4
|
project: any,
|
|
@@ -8,14 +8,15 @@ export async function buildProjectSummary(
|
|
|
8
8
|
agentTasks: any[],
|
|
9
9
|
roadmapItems: any[],
|
|
10
10
|
stackDef: any,
|
|
11
|
-
supabase: any
|
|
11
|
+
supabase: any,
|
|
12
|
+
userId: string
|
|
12
13
|
): Promise<string> {
|
|
13
14
|
const summaryParts: string[] = [];
|
|
14
15
|
|
|
15
16
|
summaryParts.push(`Project Type: ${project.project_type?.toUpperCase() || 'UNKNOWN'}`);
|
|
16
17
|
|
|
17
18
|
// Active Mission Parameters
|
|
18
|
-
if (stackDef) {
|
|
19
|
+
if (stackDef || activeTask || nextTask) {
|
|
19
20
|
summaryParts.push('\n=== ACTIVE MISSION PARAMETERS ===');
|
|
20
21
|
if (activeTask) {
|
|
21
22
|
summaryParts.push(`⚠️ CURRENT OBJECTIVE: T-${activeTask.step_number}: ${activeTask.title}`);
|
|
@@ -44,17 +45,23 @@ export async function buildProjectSummary(
|
|
|
44
45
|
summaryParts.push(` Tags: ${activeTask.tags.join(', ')}`);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
// Enhanced Feature Context
|
|
48
|
+
// Enhanced Feature Context (Secure)
|
|
48
49
|
if (activeTask.feature_id) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
try {
|
|
51
|
+
const { data: dbFeatures } = await supabase
|
|
52
|
+
.rpc('get_project_features_secure', {
|
|
53
|
+
p_project_id: project.id,
|
|
54
|
+
p_user_id: userId
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const feature = dbFeatures?.find((f: any) => f.id === activeTask.feature_id);
|
|
58
|
+
|
|
59
|
+
if (feature) {
|
|
60
|
+
summaryParts.push(`\n Parent Feature: ${feature.name}`);
|
|
61
|
+
summaryParts.push(` Feature Vision: ${feature.description}`);
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.warn('Feature context fetch failed', e);
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
|
|
@@ -80,13 +87,15 @@ export async function buildProjectSummary(
|
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
summaryParts.push('\n=== CURRENT STACK ===');
|
|
83
|
-
if (stackDef
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
if (stackDef) {
|
|
91
|
+
if (stackDef.frontend) summaryParts.push(`Frontend: ${stackDef.frontend.framework} (${stackDef.frontend.language})`);
|
|
92
|
+
if (stackDef.backend) summaryParts.push(`Backend: ${stackDef.backend.service} (${stackDef.backend.database})`);
|
|
93
|
+
if (stackDef.styling) summaryParts.push(`Styling: ${stackDef.styling.framework} ${stackDef.styling.library || ''}`);
|
|
94
|
+
if (stackDef.hosting) summaryParts.push(`Infrastructure: ${stackDef.hosting.provider}`);
|
|
95
|
+
} else {
|
|
96
|
+
if (techStack.framework) summaryParts.push(`Framework: ${techStack.framework}`);
|
|
97
|
+
if (techStack.orm) summaryParts.push(`ORM: ${techStack.orm}`);
|
|
98
|
+
}
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
if (project.description) {
|
|
@@ -103,8 +112,8 @@ export async function buildProjectSummary(
|
|
|
103
112
|
|
|
104
113
|
if (spec.featureList && Array.isArray(spec.featureList)) {
|
|
105
114
|
summaryParts.push('\nKey Features & Nuances:');
|
|
106
|
-
spec.featureList.filter((f: any) => f.priority === 'MVP').forEach((f: any) => {
|
|
107
|
-
summaryParts.push(`- ${f.name}: ${f.description}`);
|
|
115
|
+
spec.featureList.filter((f: any) => f.priority === 'MVP' || f.priority === 'HIGH').slice(0, 10).forEach((f: any) => {
|
|
116
|
+
summaryParts.push(`- ${f.name}: ${f.description?.substring(0, 200)}`);
|
|
108
117
|
});
|
|
109
118
|
}
|
|
110
119
|
}
|
|
@@ -124,7 +133,7 @@ export async function buildProjectSummary(
|
|
|
124
133
|
summaryParts.push('\nLatest AI Executions:');
|
|
125
134
|
agentTasks.forEach((t: any) => {
|
|
126
135
|
const time = t.completed_at ? new Date(t.completed_at).toLocaleString() : 'Recently';
|
|
127
|
-
summaryParts.push(`- [${time}] ${t.roadmap_title || 'Task'}: ${t.execution_summary || 'Completed'}`);
|
|
136
|
+
summaryParts.push(`- [${time}] ${t.roadmap_title || 'Task'}: ${t.execution_summary?.substring(0, 100) || 'Completed'}`);
|
|
128
137
|
});
|
|
129
138
|
}
|
|
130
139
|
|
|
@@ -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
|
|
@@ -165,7 +155,6 @@ export async function getProjectContext(
|
|
|
165
155
|
)].slice(0, 10) as string[];
|
|
166
156
|
}
|
|
167
157
|
|
|
168
|
-
// Build summary via utility
|
|
169
158
|
const summary = await buildProjectSummary(
|
|
170
159
|
project,
|
|
171
160
|
techStack,
|
|
@@ -174,7 +163,8 @@ export async function getProjectContext(
|
|
|
174
163
|
agentTasks || [],
|
|
175
164
|
roadmapItems || [],
|
|
176
165
|
stackDef,
|
|
177
|
-
supabase
|
|
166
|
+
supabase,
|
|
167
|
+
userId
|
|
178
168
|
);
|
|
179
169
|
|
|
180
170
|
const response: ProjectContextResponse = {
|
|
@@ -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,53 +17,45 @@ 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
|
-
p_user_id: userId
|
|
25
|
-
});
|
|
20
|
+
// 1. Fetch project and features
|
|
21
|
+
let projectRow: any = null;
|
|
22
|
+
let dbFeatures: any[] = [];
|
|
23
|
+
let source = 'DB';
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
try {
|
|
26
|
+
// Try secure gateway for project
|
|
27
|
+
const { data } = await supabase
|
|
28
|
+
.rpc('get_project_context_secure', { p_project_id: projectId, p_user_id: userId })
|
|
29
|
+
.single() as any;
|
|
30
|
+
projectRow = data;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
// Fallback
|
|
33
|
+
const { data } = await supabase.from('projects').select('id, functional_spec').eq('id', projectId).single();
|
|
34
|
+
projectRow = data;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
const { data: project, error: projectError } = await supabase
|
|
33
|
-
.from('projects')
|
|
34
|
-
.select('id, functional_spec')
|
|
35
|
-
.eq('id', projectId)
|
|
36
|
-
.single();
|
|
37
|
+
if (!projectRow) throw new Error(`Project ${projectId} not found or access denied.`);
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
try {
|
|
40
|
+
// Try secure gateway for features
|
|
41
|
+
const { data } = await supabase.rpc('get_project_features_secure', { p_project_id: projectId, p_user_id: userId });
|
|
42
|
+
dbFeatures = data || [];
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// Fallback to direct table
|
|
45
|
+
const { data } = await supabase.from('project_features').select('id, name, description, status').eq('project_id', projectId);
|
|
46
|
+
dbFeatures = data || [];
|
|
40
47
|
}
|
|
41
48
|
|
|
42
|
-
// 2. Primary Strategy: Fetch from 'project_features'
|
|
43
|
-
const { data: dbFeatures, error: dbError } = await supabase
|
|
44
|
-
.from('project_features')
|
|
45
|
-
.select('id, name, description, status')
|
|
46
|
-
.eq('project_id', projectId)
|
|
47
|
-
.neq('status', 'shadow') // Exclude shadow features by default unless asked
|
|
48
|
-
.order('created_at', { ascending: false });
|
|
49
|
-
|
|
50
49
|
let featuresList: any[] = [];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!dbError && dbFeatures && dbFeatures.length > 0) {
|
|
54
|
-
featuresList = dbFeatures.map(f => ({
|
|
55
|
-
...f,
|
|
56
|
-
title: f.name // Map back to title specifically for uniform handling below
|
|
57
|
-
}));
|
|
50
|
+
if (dbFeatures && dbFeatures.length > 0) {
|
|
51
|
+
featuresList = dbFeatures.map((f: any) => ({ ...f, title: f.name }));
|
|
58
52
|
} else {
|
|
59
|
-
//
|
|
53
|
+
// Fallback to spec
|
|
60
54
|
source = 'FALLBACK_SPEC';
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (spec && typeof spec === 'object' && Array.isArray(spec.features)) {
|
|
66
|
-
featuresList = spec.features.map((f: any) => ({
|
|
55
|
+
const spec = projectRow.functional_spec as any;
|
|
56
|
+
const features = spec?.featureList || spec?.features;
|
|
57
|
+
if (Array.isArray(features)) {
|
|
58
|
+
featuresList = features.map((f: any) => ({
|
|
67
59
|
id: 'legacy',
|
|
68
60
|
title: f.name || f.title,
|
|
69
61
|
description: f.description,
|
|
@@ -72,22 +64,15 @@ Useful for understanding the strategic context and major milestones.`,
|
|
|
72
64
|
}
|
|
73
65
|
}
|
|
74
66
|
|
|
75
|
-
// 4. Format Response
|
|
76
67
|
if (featuresList.length === 0) {
|
|
77
|
-
return { content: [{ type: 'text', text: 'No active features found
|
|
68
|
+
return { content: [{ type: 'text', text: 'No active features found.' }] };
|
|
78
69
|
}
|
|
79
70
|
|
|
80
71
|
const formatted = `=== PROJECT FEATURES (Source: ${source}) ===\n` +
|
|
81
|
-
featuresList.map(f => {
|
|
82
|
-
return `- ${f.title} [${f.status}]`;
|
|
83
|
-
}).join('\n');
|
|
72
|
+
featuresList.map(f => `- ${f.title} [${f.status}]`).join('\n');
|
|
84
73
|
|
|
85
|
-
return {
|
|
86
|
-
content: [{ type: 'text', text: formatted }]
|
|
87
|
-
};
|
|
74
|
+
return { content: [{ type: 'text', text: formatted }] };
|
|
88
75
|
}
|
|
89
76
|
};
|
|
90
77
|
|
|
91
78
|
registry.register(listFeaturesTool);
|
|
92
|
-
|
|
93
|
-
|
|
@@ -60,25 +60,56 @@ export async function saveToProjectBrain(
|
|
|
60
60
|
|
|
61
61
|
const fullContent = `# ${title}\n\n${content}`;
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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', {
|
|
69
|
+
p_project_id: projectId,
|
|
70
|
+
p_user_id: userId,
|
|
71
|
+
p_content: fullContent,
|
|
72
|
+
p_category: category.toLowerCase(),
|
|
73
|
+
p_tags: tags || [],
|
|
74
|
+
p_importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5
|
|
75
|
+
});
|
|
76
|
+
if (data && !error) {
|
|
77
|
+
memoryId = data;
|
|
78
|
+
} else {
|
|
79
|
+
saveError = error;
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
saveError = e;
|
|
83
|
+
}
|
|
76
84
|
|
|
77
|
-
|
|
85
|
+
// 2. Fallback to direct insert (RLS was updated to allow members)
|
|
86
|
+
if (!memoryId) {
|
|
87
|
+
console.warn('RPC save_project_memory_secure failed, trying direct insert. Error:', saveError);
|
|
88
|
+
const { data, error } = await supabase
|
|
89
|
+
.from('project_memories')
|
|
90
|
+
.insert({
|
|
91
|
+
project_id: projectId,
|
|
92
|
+
content: fullContent,
|
|
93
|
+
category: category.toLowerCase(),
|
|
94
|
+
tags: tags || [],
|
|
95
|
+
importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5,
|
|
96
|
+
is_active: true,
|
|
97
|
+
source_type: 'chat_manual'
|
|
98
|
+
})
|
|
99
|
+
.select('id')
|
|
100
|
+
.single();
|
|
101
|
+
|
|
102
|
+
if (data) {
|
|
103
|
+
memoryId = data.id;
|
|
104
|
+
} else {
|
|
105
|
+
console.error('Direct insert failed:', error);
|
|
106
|
+
throw new Error(`Failed to save memory: ${error?.message || 'Unknown error'}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
78
109
|
|
|
79
110
|
return {
|
|
80
111
|
success: true,
|
|
81
|
-
memoryId:
|
|
112
|
+
memoryId: memoryId,
|
|
82
113
|
message: `✅ Saved [${category}] "${title}" to Project Brain.`
|
|
83
114
|
};
|
|
84
115
|
}
|