@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/dist/index.js
CHANGED
|
@@ -1903,10 +1903,10 @@ var CompleteRoadmapTaskInputSchema = z4.object({
|
|
|
1903
1903
|
|
|
1904
1904
|
// src/lib/project-context-utils.ts
|
|
1905
1905
|
init_esm_shims();
|
|
1906
|
-
async function buildProjectSummary(project, techStack, activeTask, nextTask, agentTasks, roadmapItems, stackDef, supabase) {
|
|
1906
|
+
async function buildProjectSummary(project, techStack, activeTask, nextTask, agentTasks, roadmapItems, stackDef, supabase, userId) {
|
|
1907
1907
|
const summaryParts = [];
|
|
1908
1908
|
summaryParts.push(`Project Type: ${project.project_type?.toUpperCase() || "UNKNOWN"}`);
|
|
1909
|
-
if (stackDef) {
|
|
1909
|
+
if (stackDef || activeTask || nextTask) {
|
|
1910
1910
|
summaryParts.push("\n=== ACTIVE MISSION PARAMETERS ===");
|
|
1911
1911
|
if (activeTask) {
|
|
1912
1912
|
summaryParts.push(`\u26A0\uFE0F CURRENT OBJECTIVE: T-${activeTask.step_number}: ${activeTask.title}`);
|
|
@@ -1937,11 +1937,19 @@ async function buildProjectSummary(project, techStack, activeTask, nextTask, age
|
|
|
1937
1937
|
summaryParts.push(` Tags: ${activeTask.tags.join(", ")}`);
|
|
1938
1938
|
}
|
|
1939
1939
|
if (activeTask.feature_id) {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1940
|
+
try {
|
|
1941
|
+
const { data: dbFeatures } = await supabase.rpc("get_project_features_secure", {
|
|
1942
|
+
p_project_id: project.id,
|
|
1943
|
+
p_user_id: userId
|
|
1944
|
+
});
|
|
1945
|
+
const feature = dbFeatures?.find((f) => f.id === activeTask.feature_id);
|
|
1946
|
+
if (feature) {
|
|
1947
|
+
summaryParts.push(`
|
|
1943
1948
|
Parent Feature: ${feature.name}`);
|
|
1944
|
-
|
|
1949
|
+
summaryParts.push(` Feature Vision: ${feature.description}`);
|
|
1950
|
+
}
|
|
1951
|
+
} catch (e) {
|
|
1952
|
+
console.warn("Feature context fetch failed", e);
|
|
1945
1953
|
}
|
|
1946
1954
|
}
|
|
1947
1955
|
summaryParts.push("\n ACTION: Focus ALL coding efforts on completing this task.");
|
|
@@ -1964,13 +1972,15 @@ async function buildProjectSummary(project, techStack, activeTask, nextTask, age
|
|
|
1964
1972
|
summaryParts.push(`2. PROACTIVE: Instead of asking "How can I help?", ask "Shall we start on T-${nextTask.step_number}?"`);
|
|
1965
1973
|
}
|
|
1966
1974
|
summaryParts.push("\n=== CURRENT STACK ===");
|
|
1967
|
-
if (stackDef
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1975
|
+
if (stackDef) {
|
|
1976
|
+
if (stackDef.frontend) summaryParts.push(`Frontend: ${stackDef.frontend.framework} (${stackDef.frontend.language})`);
|
|
1977
|
+
if (stackDef.backend) summaryParts.push(`Backend: ${stackDef.backend.service} (${stackDef.backend.database})`);
|
|
1978
|
+
if (stackDef.styling) summaryParts.push(`Styling: ${stackDef.styling.framework} ${stackDef.styling.library || ""}`);
|
|
1979
|
+
if (stackDef.hosting) summaryParts.push(`Infrastructure: ${stackDef.hosting.provider}`);
|
|
1980
|
+
} else {
|
|
1981
|
+
if (techStack.framework) summaryParts.push(`Framework: ${techStack.framework}`);
|
|
1982
|
+
if (techStack.orm) summaryParts.push(`ORM: ${techStack.orm}`);
|
|
1983
|
+
}
|
|
1974
1984
|
}
|
|
1975
1985
|
if (project.description) {
|
|
1976
1986
|
summaryParts.push(`
|
|
@@ -1984,8 +1994,8 @@ Description: ${project.description}`);
|
|
|
1984
1994
|
if (spec.coreProblem) summaryParts.push(`Core Problem: ${spec.coreProblem}`);
|
|
1985
1995
|
if (spec.featureList && Array.isArray(spec.featureList)) {
|
|
1986
1996
|
summaryParts.push("\nKey Features & Nuances:");
|
|
1987
|
-
spec.featureList.filter((f) => f.priority === "MVP").forEach((f) => {
|
|
1988
|
-
summaryParts.push(`- ${f.name}: ${f.description}`);
|
|
1997
|
+
spec.featureList.filter((f) => f.priority === "MVP" || f.priority === "HIGH").slice(0, 10).forEach((f) => {
|
|
1998
|
+
summaryParts.push(`- ${f.name}: ${f.description?.substring(0, 200)}`);
|
|
1989
1999
|
});
|
|
1990
2000
|
}
|
|
1991
2001
|
}
|
|
@@ -2002,7 +2012,7 @@ Description: ${project.description}`);
|
|
|
2002
2012
|
summaryParts.push("\nLatest AI Executions:");
|
|
2003
2013
|
agentTasks.forEach((t) => {
|
|
2004
2014
|
const time = t.completed_at ? new Date(t.completed_at).toLocaleString() : "Recently";
|
|
2005
|
-
summaryParts.push(`- [${time}] ${t.roadmap_title || "Task"}: ${t.execution_summary || "Completed"}`);
|
|
2015
|
+
summaryParts.push(`- [${time}] ${t.roadmap_title || "Task"}: ${t.execution_summary?.substring(0, 100) || "Completed"}`);
|
|
2006
2016
|
});
|
|
2007
2017
|
}
|
|
2008
2018
|
if (roadmapItems && roadmapItems.length > 0) {
|
|
@@ -2042,26 +2052,38 @@ var KEY_LIBS = {
|
|
|
2042
2052
|
"ai": "Vercel AI"
|
|
2043
2053
|
};
|
|
2044
2054
|
async function getProjectContext(supabase, userId, projectId) {
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2055
|
+
let projectRow = null;
|
|
2056
|
+
let projectError = null;
|
|
2057
|
+
try {
|
|
2058
|
+
const { data, error } = await supabase.rpc("get_project_context_secure", {
|
|
2059
|
+
p_project_id: projectId,
|
|
2060
|
+
p_user_id: userId
|
|
2061
|
+
}).single();
|
|
2062
|
+
if (data && !error) {
|
|
2063
|
+
projectRow = data;
|
|
2064
|
+
} else {
|
|
2065
|
+
projectError = error;
|
|
2066
|
+
}
|
|
2067
|
+
} catch (e) {
|
|
2068
|
+
projectError = e;
|
|
2069
|
+
}
|
|
2070
|
+
if (!projectRow) {
|
|
2071
|
+
console.error(`RPC get_project_context_secure failed for ${projectId}, falling back to direct query. Error:`, projectError);
|
|
2072
|
+
const { data, error } = await supabase.from("projects").select(`
|
|
2073
|
+
id, name, description, project_type, created_at, last_indexed_at, detected_stack, repository_tree, functional_spec,
|
|
2074
|
+
architectural_dna(stack_definition)
|
|
2075
|
+
`).eq("id", projectId).single();
|
|
2076
|
+
if (data) {
|
|
2077
|
+
projectRow = {
|
|
2078
|
+
...data,
|
|
2079
|
+
architectural_dna: Array.isArray(data.architectural_dna) ? data.architectural_dna[0] : data.architectural_dna
|
|
2080
|
+
};
|
|
2081
|
+
} else {
|
|
2082
|
+
console.error("Project fetch failed completely:", error);
|
|
2083
|
+
throw new Error(`Project ${projectId} not found or access denied. (User: ${userId})`);
|
|
2084
|
+
}
|
|
2053
2085
|
}
|
|
2054
|
-
const project =
|
|
2055
|
-
id: projectRow.id,
|
|
2056
|
-
name: projectRow.name,
|
|
2057
|
-
description: projectRow.description,
|
|
2058
|
-
project_type: projectRow.project_type,
|
|
2059
|
-
created_at: projectRow.created_at,
|
|
2060
|
-
last_indexed_at: projectRow.last_indexed_at,
|
|
2061
|
-
detected_stack: projectRow.detected_stack,
|
|
2062
|
-
repository_tree: projectRow.repository_tree,
|
|
2063
|
-
functional_spec: projectRow.functional_spec
|
|
2064
|
-
};
|
|
2086
|
+
const project = projectRow;
|
|
2065
2087
|
const stackDef = projectRow.architectural_dna?.stack_definition;
|
|
2066
2088
|
const { data: allChunks } = await supabase.rpc("get_roadmap_chunks_secure", {
|
|
2067
2089
|
p_project_id: projectId,
|
|
@@ -2089,13 +2111,9 @@ async function getProjectContext(supabase, userId, projectId) {
|
|
|
2089
2111
|
for (const [name, version] of Object.entries(allDeps)) {
|
|
2090
2112
|
const cleanVersion = version.replace(/[\^~]/g, "");
|
|
2091
2113
|
if (name === "next" || name === "react" || name === "vue" || name === "angular" || name === "svelte") {
|
|
2092
|
-
if (!techStack.framework) {
|
|
2093
|
-
techStack.framework = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
2094
|
-
}
|
|
2114
|
+
if (!techStack.framework) techStack.framework = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
2095
2115
|
} else if (name.includes("prisma") || name.includes("drizzle") || name.includes("typeorm")) {
|
|
2096
|
-
if (!techStack.orm) {
|
|
2097
|
-
techStack.orm = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
2098
|
-
}
|
|
2116
|
+
if (!techStack.orm) techStack.orm = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
2099
2117
|
} else if (KEY_LIBS[name]) {
|
|
2100
2118
|
techStack.keyLibraries.push(`${KEY_LIBS[name]} ${cleanVersion}`);
|
|
2101
2119
|
}
|
|
@@ -2114,7 +2132,8 @@ async function getProjectContext(supabase, userId, projectId) {
|
|
|
2114
2132
|
agentTasks || [],
|
|
2115
2133
|
roadmapItems || [],
|
|
2116
2134
|
stackDef,
|
|
2117
|
-
supabase
|
|
2135
|
+
supabase,
|
|
2136
|
+
userId
|
|
2118
2137
|
);
|
|
2119
2138
|
const response = {
|
|
2120
2139
|
project: {
|
|
@@ -2133,13 +2152,10 @@ async function getProjectContext(supabase, userId, projectId) {
|
|
|
2133
2152
|
frameworks: techStack.framework ? [techStack.framework] : [],
|
|
2134
2153
|
libraries: techStack.keyLibraries
|
|
2135
2154
|
});
|
|
2136
|
-
if (curatorContext)
|
|
2137
|
-
response.summary += `
|
|
2155
|
+
if (curatorContext) response.summary += `
|
|
2138
2156
|
|
|
2139
2157
|
=== CURATOR INTELLIGENCE ===${curatorContext}`;
|
|
2140
|
-
}
|
|
2141
2158
|
} catch (e) {
|
|
2142
|
-
console.error("Failed to inject global context:", e);
|
|
2143
2159
|
response.summary += `
|
|
2144
2160
|
|
|
2145
2161
|
(Curator Context Unavailable: ${e.message})`;
|
|
@@ -2151,102 +2167,60 @@ async function getProjectContext(supabase, userId, projectId) {
|
|
|
2151
2167
|
init_esm_shims();
|
|
2152
2168
|
registry.register({
|
|
2153
2169
|
name: "query_brain",
|
|
2154
|
-
description: `Takes a natural language query and performs semantic search
|
|
2155
|
-
against the project's memories (RAG), returning relevant
|
|
2156
|
-
architecture rules, decisions, and constraints.`,
|
|
2170
|
+
description: `Takes a natural language query and performs semantic search against the project's memories (RAG).`,
|
|
2157
2171
|
schema: QueryBrainInputSchema,
|
|
2158
2172
|
handler: async (args, context) => {
|
|
2159
|
-
const result = await queryBrain(
|
|
2160
|
-
context.supabase,
|
|
2161
|
-
context.userId,
|
|
2162
|
-
args.projectId,
|
|
2163
|
-
args.query,
|
|
2164
|
-
args.limit,
|
|
2165
|
-
args.threshold
|
|
2166
|
-
);
|
|
2173
|
+
const result = await queryBrain(context.supabase, context.userId, args.projectId, args.query, args.limit);
|
|
2167
2174
|
return { content: [{ type: "text", text: result.formatted }] };
|
|
2168
2175
|
}
|
|
2169
2176
|
});
|
|
2170
|
-
async function
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
if (!apiKey) {
|
|
2174
|
-
return null;
|
|
2175
|
-
}
|
|
2177
|
+
async function queryBrain(supabase, userId, projectId, query, limit = 8) {
|
|
2178
|
+
let memories = [];
|
|
2179
|
+
let searchType = "SECURE-GATEWAY";
|
|
2176
2180
|
try {
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
},
|
|
2183
|
-
body: JSON.stringify({ text: query })
|
|
2181
|
+
const { data, error } = await supabase.rpc("query_project_brain_secure", {
|
|
2182
|
+
p_project_id: projectId,
|
|
2183
|
+
p_user_id: userId,
|
|
2184
|
+
p_query: query,
|
|
2185
|
+
p_limit: limit
|
|
2184
2186
|
});
|
|
2185
|
-
if (!
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2187
|
+
if (!error && data) {
|
|
2188
|
+
memories = data.map((m) => ({
|
|
2189
|
+
id: m.id,
|
|
2190
|
+
content: m.content,
|
|
2191
|
+
category: m.category,
|
|
2192
|
+
tags: m.tags,
|
|
2193
|
+
netVotes: m.importance,
|
|
2194
|
+
createdAt: m.created_at
|
|
2195
|
+
}));
|
|
2196
|
+
} else {
|
|
2197
|
+
throw error || new Error("No data");
|
|
2189
2198
|
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
async function queryBrain(supabase, userId, projectId, query, limit = 8, threshold = 0.5) {
|
|
2198
|
-
const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
|
|
2199
|
-
p_project_id: projectId,
|
|
2200
|
-
p_user_id: userId
|
|
2201
|
-
});
|
|
2202
|
-
if (accessError || !hasAccess) {
|
|
2203
|
-
throw new Error("Project not found or access denied");
|
|
2204
|
-
}
|
|
2205
|
-
const embedding = await generateQueryEmbedding(query);
|
|
2206
|
-
let memories = [];
|
|
2207
|
-
const { data: searchResults, error: searchError } = await supabase.rpc("hybrid_search_memories", {
|
|
2208
|
-
p_project_id: projectId,
|
|
2209
|
-
p_query: query,
|
|
2210
|
-
p_embedding: embedding || null,
|
|
2211
|
-
p_limit: limit,
|
|
2212
|
-
p_similarity_threshold: threshold || 0.1
|
|
2213
|
-
});
|
|
2214
|
-
if (searchError) {
|
|
2215
|
-
const { data: recentMemories } = await supabase.from("project_memories").select("id, content, category, tags, importance, created_at").eq("project_id", projectId).eq("is_active", true).order("created_at", { ascending: false }).limit(limit);
|
|
2216
|
-
if (recentMemories) {
|
|
2217
|
-
memories = recentMemories.map((m) => ({
|
|
2199
|
+
} catch (e) {
|
|
2200
|
+
searchType = "DIRECT-FALLBACK";
|
|
2201
|
+
const { data } = await supabase.from("project_memories").select("id, content, category, tags, importance, created_at").eq("project_id", projectId).eq("is_active", true).or(`content.ilike.%${query}%,category.ilike.%${query}%`).order("created_at", { ascending: false }).limit(limit);
|
|
2202
|
+
if (data) {
|
|
2203
|
+
memories = data.map((m) => ({
|
|
2218
2204
|
id: m.id,
|
|
2219
2205
|
content: m.content,
|
|
2220
|
-
category: m.category
|
|
2221
|
-
tags: m.tags
|
|
2222
|
-
netVotes: m.importance
|
|
2206
|
+
category: m.category,
|
|
2207
|
+
tags: m.tags,
|
|
2208
|
+
netVotes: m.importance,
|
|
2223
2209
|
createdAt: m.created_at
|
|
2224
2210
|
}));
|
|
2225
2211
|
}
|
|
2226
|
-
} else if (searchResults) {
|
|
2227
|
-
memories = searchResults.map((m) => ({
|
|
2228
|
-
id: m.id,
|
|
2229
|
-
content: m.content,
|
|
2230
|
-
category: m.category,
|
|
2231
|
-
tags: m.tags,
|
|
2232
|
-
netVotes: m.importance,
|
|
2233
|
-
createdAt: m.created_at
|
|
2234
|
-
}));
|
|
2235
2212
|
}
|
|
2236
2213
|
let relevantFeatures = [];
|
|
2237
2214
|
try {
|
|
2238
|
-
const { data:
|
|
2239
|
-
if (
|
|
2215
|
+
const { data: projectRow } = await supabase.from("projects").select("functional_spec").eq("id", projectId).single();
|
|
2216
|
+
if (projectRow?.functional_spec) {
|
|
2217
|
+
const spec = projectRow.functional_spec;
|
|
2218
|
+
const features = spec.featureList || spec.features || [];
|
|
2219
|
+
relevantFeatures = features.filter((f) => (f.name || f.title)?.toLowerCase().includes(query.toLowerCase())).slice(0, 3);
|
|
2220
|
+
}
|
|
2240
2221
|
} catch (e) {
|
|
2241
|
-
console.warn("Feature fetch failed in brain query", e);
|
|
2242
2222
|
}
|
|
2243
|
-
const contextLines = memories.map((m) => {
|
|
2244
|
-
const voteIndicator = m.netVotes && m.netVotes < 0 ? ` [\u26A0\uFE0F POORLY RATED: ${m.netVotes}]` : "";
|
|
2245
|
-
const tagStr = m.tags && m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
2246
|
-
const category = m.category ? m.category.toUpperCase() : "GENERAL";
|
|
2247
|
-
return `- [${category}]${tagStr}${voteIndicator}: ${m.content}`;
|
|
2248
|
-
});
|
|
2249
|
-
const searchType = embedding ? "TRIPLE-HYBRID (Vector + FTS + Fuzzy)" : "HYBRID (FTS + Fuzzy)";
|
|
2223
|
+
const contextLines = memories.map((m) => `- [${(m.category || "GENERAL").toUpperCase()}]: ${m.content}`);
|
|
2250
2224
|
let formatted = `=== PROJECT BRAIN: RELEVANT MEMORIES ===
|
|
2251
2225
|
Search Mode: ${searchType}
|
|
2252
2226
|
Query: "${query}"`;
|
|
@@ -2254,26 +2228,18 @@ Query: "${query}"`;
|
|
|
2254
2228
|
formatted += `
|
|
2255
2229
|
|
|
2256
2230
|
=== RELATED FEATURES ===
|
|
2257
|
-
` + relevantFeatures.map((f) => `- ${f.name} [${f.status}]`).join("\n");
|
|
2231
|
+
` + relevantFeatures.map((f) => `- ${f.name || f.title} [${f.status || "Active"}]`).join("\n");
|
|
2258
2232
|
}
|
|
2259
2233
|
formatted += `
|
|
2260
2234
|
|
|
2261
2235
|
Found ${memories.length} relevant memories:
|
|
2262
2236
|
|
|
2263
|
-
${contextLines.join("\n")}
|
|
2264
|
-
|
|
2265
|
-
==========================================`;
|
|
2237
|
+
${contextLines.join("\n")}`;
|
|
2266
2238
|
if (memories.length === 0 && relevantFeatures.length === 0) {
|
|
2267
2239
|
formatted = `=== PROJECT BRAIN ===
|
|
2268
|
-
|
|
2269
|
-
No relevant memories or features found.
|
|
2270
|
-
=======================`;
|
|
2240
|
+
No relevant info found for "${query}".`;
|
|
2271
2241
|
}
|
|
2272
|
-
return {
|
|
2273
|
-
query,
|
|
2274
|
-
memories,
|
|
2275
|
-
formatted
|
|
2276
|
-
};
|
|
2242
|
+
return { query, memories, formatted };
|
|
2277
2243
|
}
|
|
2278
2244
|
|
|
2279
2245
|
// src/tools/get-latest-decisions.ts
|
|
@@ -2818,32 +2784,33 @@ var listFeaturesTool = {
|
|
|
2818
2784
|
Useful for understanding the strategic context and major milestones.`,
|
|
2819
2785
|
schema: InputSchema,
|
|
2820
2786
|
handler: async ({ projectId }, { supabase, userId }) => {
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2787
|
+
let projectRow = null;
|
|
2788
|
+
let dbFeatures = [];
|
|
2789
|
+
let source = "DB";
|
|
2790
|
+
try {
|
|
2791
|
+
const { data } = await supabase.rpc("get_project_context_secure", { p_project_id: projectId, p_user_id: userId }).single();
|
|
2792
|
+
projectRow = data;
|
|
2793
|
+
} catch (e) {
|
|
2794
|
+
const { data } = await supabase.from("projects").select("id, functional_spec").eq("id", projectId).single();
|
|
2795
|
+
projectRow = data;
|
|
2827
2796
|
}
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2797
|
+
if (!projectRow) throw new Error(`Project ${projectId} not found or access denied.`);
|
|
2798
|
+
try {
|
|
2799
|
+
const { data } = await supabase.rpc("get_project_features_secure", { p_project_id: projectId, p_user_id: userId });
|
|
2800
|
+
dbFeatures = data || [];
|
|
2801
|
+
} catch (e) {
|
|
2802
|
+
const { data } = await supabase.from("project_features").select("id, name, description, status").eq("project_id", projectId);
|
|
2803
|
+
dbFeatures = data || [];
|
|
2831
2804
|
}
|
|
2832
|
-
const { data: dbFeatures, error: dbError } = await supabase.from("project_features").select("id, name, description, status").eq("project_id", projectId).neq("status", "shadow").order("created_at", { ascending: false });
|
|
2833
2805
|
let featuresList = [];
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
featuresList = dbFeatures.map((f) => ({
|
|
2837
|
-
...f,
|
|
2838
|
-
title: f.name
|
|
2839
|
-
// Map back to title specifically for uniform handling below
|
|
2840
|
-
}));
|
|
2806
|
+
if (dbFeatures && dbFeatures.length > 0) {
|
|
2807
|
+
featuresList = dbFeatures.map((f) => ({ ...f, title: f.name }));
|
|
2841
2808
|
} else {
|
|
2842
2809
|
source = "FALLBACK_SPEC";
|
|
2843
|
-
|
|
2844
|
-
const
|
|
2845
|
-
if (
|
|
2846
|
-
featuresList =
|
|
2810
|
+
const spec = projectRow.functional_spec;
|
|
2811
|
+
const features = spec?.featureList || spec?.features;
|
|
2812
|
+
if (Array.isArray(features)) {
|
|
2813
|
+
featuresList = features.map((f) => ({
|
|
2847
2814
|
id: "legacy",
|
|
2848
2815
|
title: f.name || f.title,
|
|
2849
2816
|
description: f.description,
|
|
@@ -2852,15 +2819,11 @@ Useful for understanding the strategic context and major milestones.`,
|
|
|
2852
2819
|
}
|
|
2853
2820
|
}
|
|
2854
2821
|
if (featuresList.length === 0) {
|
|
2855
|
-
return { content: [{ type: "text", text: "No active features found
|
|
2822
|
+
return { content: [{ type: "text", text: "No active features found." }] };
|
|
2856
2823
|
}
|
|
2857
2824
|
const formatted = `=== PROJECT FEATURES (Source: ${source}) ===
|
|
2858
|
-
` + featuresList.map((f) => {
|
|
2859
|
-
|
|
2860
|
-
}).join("\n");
|
|
2861
|
-
return {
|
|
2862
|
-
content: [{ type: "text", text: formatted }]
|
|
2863
|
-
};
|
|
2825
|
+
` + featuresList.map((f) => `- ${f.title} [${f.status}]`).join("\n");
|
|
2826
|
+
return { content: [{ type: "text", text: formatted }] };
|
|
2864
2827
|
}
|
|
2865
2828
|
};
|
|
2866
2829
|
registry.register(listFeaturesTool);
|
|
@@ -3807,19 +3770,46 @@ async function saveToProjectBrain(supabase, userId, input) {
|
|
|
3807
3770
|
const fullContent = `# ${title}
|
|
3808
3771
|
|
|
3809
3772
|
${content}`;
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3773
|
+
let memoryId = null;
|
|
3774
|
+
let saveError = null;
|
|
3775
|
+
try {
|
|
3776
|
+
const { data, error } = await supabase.rpc("save_project_memory_secure", {
|
|
3777
|
+
p_project_id: projectId,
|
|
3778
|
+
p_user_id: userId,
|
|
3779
|
+
p_content: fullContent,
|
|
3780
|
+
p_category: category.toLowerCase(),
|
|
3781
|
+
p_tags: tags || [],
|
|
3782
|
+
p_importance: category === "DECISION" || category === "ARCHITECTURE" ? 9 : 5
|
|
3783
|
+
});
|
|
3784
|
+
if (data && !error) {
|
|
3785
|
+
memoryId = data;
|
|
3786
|
+
} else {
|
|
3787
|
+
saveError = error;
|
|
3788
|
+
}
|
|
3789
|
+
} catch (e) {
|
|
3790
|
+
saveError = e;
|
|
3791
|
+
}
|
|
3792
|
+
if (!memoryId) {
|
|
3793
|
+
console.warn("RPC save_project_memory_secure failed, trying direct insert. Error:", saveError);
|
|
3794
|
+
const { data, error } = await supabase.from("project_memories").insert({
|
|
3795
|
+
project_id: projectId,
|
|
3796
|
+
content: fullContent,
|
|
3797
|
+
category: category.toLowerCase(),
|
|
3798
|
+
tags: tags || [],
|
|
3799
|
+
importance: category === "DECISION" || category === "ARCHITECTURE" ? 9 : 5,
|
|
3800
|
+
is_active: true,
|
|
3801
|
+
source_type: "chat_manual"
|
|
3802
|
+
}).select("id").single();
|
|
3803
|
+
if (data) {
|
|
3804
|
+
memoryId = data.id;
|
|
3805
|
+
} else {
|
|
3806
|
+
console.error("Direct insert failed:", error);
|
|
3807
|
+
throw new Error(`Failed to save memory: ${error?.message || "Unknown error"}`);
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3820
3810
|
return {
|
|
3821
3811
|
success: true,
|
|
3822
|
-
memoryId
|
|
3812
|
+
memoryId,
|
|
3823
3813
|
message: `\u2705 Saved [${category}] "${title}" to Project Brain.`
|
|
3824
3814
|
};
|
|
3825
3815
|
}
|