@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 CHANGED
@@ -1102,7 +1102,7 @@ import { ListToolsRequestSchema, ListResourcesRequestSchema } from "@modelcontex
1102
1102
  // src/server/types.ts
1103
1103
  init_esm_shims();
1104
1104
  var SERVER_NAME = "rigstate-mcp";
1105
- var SERVER_VERSION = "0.5.0";
1105
+ var SERVER_VERSION = "0.7.5";
1106
1106
 
1107
1107
  // src/lib/tool-registry.ts
1108
1108
  init_esm_shims();
@@ -1275,8 +1275,11 @@ ${formatted}
1275
1275
  // src/lib/curator/actions/submit.ts
1276
1276
  init_esm_shims();
1277
1277
  async function submitSignal(supabase, userId, input) {
1278
- const { data: project, error: projectError } = await supabase.from("projects").select("id").eq("id", input.projectId).eq("owner_id", userId).single();
1279
- if (projectError || !project) {
1278
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
1279
+ p_project_id: input.projectId,
1280
+ p_user_id: userId
1281
+ });
1282
+ if (accessError || !hasAccess) {
1280
1283
  throw new Error("Project not found or access denied");
1281
1284
  }
1282
1285
  const fingerprintBase = `${input.instruction}::${input.category}`.toLowerCase();
@@ -1561,8 +1564,11 @@ var GetLearnedInstructionsSchema = z3.object({
1561
1564
  async function refineLogic(supabase, userId, args) {
1562
1565
  const { projectId, originalReasoning, userCorrection, scope } = args;
1563
1566
  const traceId = uuidv4();
1564
- const { data: project, error: projectError } = await supabase.from("projects").select("id, name").eq("id", projectId).eq("owner_id", userId).single();
1565
- if (projectError || !project) {
1567
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
1568
+ p_project_id: projectId,
1569
+ p_user_id: userId
1570
+ });
1571
+ if (accessError || !hasAccess) {
1566
1572
  throw new Error(`Project access denied or not found: ${projectId}`);
1567
1573
  }
1568
1574
  const { data: instruction, error: insertError } = await supabase.from("ai_instructions").insert({
@@ -1895,6 +1901,129 @@ var CompleteRoadmapTaskInputSchema = z4.object({
1895
1901
  integrityGate: z4.any().optional()
1896
1902
  });
1897
1903
 
1904
+ // src/lib/project-context-utils.ts
1905
+ init_esm_shims();
1906
+ async function buildProjectSummary(project, techStack, activeTask, nextTask, agentTasks, roadmapItems, stackDef, supabase, userId) {
1907
+ const summaryParts = [];
1908
+ summaryParts.push(`Project Type: ${project.project_type?.toUpperCase() || "UNKNOWN"}`);
1909
+ if (stackDef || activeTask || nextTask) {
1910
+ summaryParts.push("\n=== ACTIVE MISSION PARAMETERS ===");
1911
+ if (activeTask) {
1912
+ summaryParts.push(`\u26A0\uFE0F CURRENT OBJECTIVE: T-${activeTask.step_number}: ${activeTask.title}`);
1913
+ summaryParts.push(` Role: ${activeTask.role || "Developer"}`);
1914
+ const detailedInstructions = activeTask.prompt_content || activeTask.instruction_set;
1915
+ if (detailedInstructions) {
1916
+ summaryParts.push(` Instructions: ${detailedInstructions.substring(0, 1e3)}...`);
1917
+ }
1918
+ if (activeTask.architectural_brief) {
1919
+ summaryParts.push(`
1920
+ Architectural Brief: ${activeTask.architectural_brief}`);
1921
+ }
1922
+ if (activeTask.context_summary) {
1923
+ summaryParts.push(`
1924
+ Context: ${activeTask.context_summary}`);
1925
+ }
1926
+ if (activeTask.checklist && activeTask.checklist.length > 0) {
1927
+ summaryParts.push("\n Checklist (DoD):");
1928
+ activeTask.checklist.forEach((item) => {
1929
+ summaryParts.push(` [ ] ${typeof item === "string" ? item : item.task}`);
1930
+ });
1931
+ }
1932
+ if (activeTask.metadata && Object.keys(activeTask.metadata).length > 0) {
1933
+ summaryParts.push(`
1934
+ Technical Metadata: ${JSON.stringify(activeTask.metadata)}`);
1935
+ }
1936
+ if (activeTask.tags && activeTask.tags.length > 0) {
1937
+ summaryParts.push(` Tags: ${activeTask.tags.join(", ")}`);
1938
+ }
1939
+ if (activeTask.feature_id) {
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(`
1948
+ Parent Feature: ${feature.name}`);
1949
+ summaryParts.push(` Feature Vision: ${feature.description}`);
1950
+ }
1951
+ } catch (e) {
1952
+ console.warn("Feature context fetch failed", e);
1953
+ }
1954
+ }
1955
+ summaryParts.push("\n ACTION: Focus ALL coding efforts on completing this task.");
1956
+ } else if (nextTask) {
1957
+ summaryParts.push(`\u23F8 SYSTEM IDLE (Waiting for command)`);
1958
+ summaryParts.push(` Suggested Next Mission: T-${nextTask.step_number}: ${nextTask.title}`);
1959
+ if (nextTask.tags && nextTask.tags.length > 0) {
1960
+ summaryParts.push(` Scope: ${nextTask.tags.join(", ")}`);
1961
+ }
1962
+ summaryParts.push(` ACTION: Ask the user "Shall we start T-${nextTask.step_number}?"`);
1963
+ } else {
1964
+ summaryParts.push("\u2705 ALL MISSIONS COMPLETE. Awaiting new roadmap items.");
1965
+ }
1966
+ summaryParts.push("\n=== AI BEHAVIORAL INSTRUCTIONS ===");
1967
+ if (activeTask) {
1968
+ summaryParts.push(`1. FOCUS: The user is working on T-${activeTask.step_number}. Help them complete it.`);
1969
+ summaryParts.push(`2. COMPLIANCE: Ensure all code follows project standards.`);
1970
+ } else if (nextTask) {
1971
+ summaryParts.push(`1. NUDGE: No active task found. Suggest starting T-${nextTask.step_number} (${nextTask.title}).`);
1972
+ summaryParts.push(`2. PROACTIVE: Instead of asking "How can I help?", ask "Shall we start on T-${nextTask.step_number}?"`);
1973
+ }
1974
+ summaryParts.push("\n=== CURRENT STACK ===");
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
+ }
1984
+ }
1985
+ if (project.description) {
1986
+ summaryParts.push(`
1987
+ Description: ${project.description}`);
1988
+ }
1989
+ if (project.functional_spec) {
1990
+ summaryParts.push("\n=== STRATEGIC PROJECT SPECIFICATION (NUANCES) ===");
1991
+ const spec = typeof project.functional_spec === "string" ? JSON.parse(project.functional_spec) : project.functional_spec;
1992
+ if (spec.projectDescription) summaryParts.push(`Vision: ${spec.projectDescription}`);
1993
+ if (spec.targetAudience) summaryParts.push(`Audience: ${spec.targetAudience}`);
1994
+ if (spec.coreProblem) summaryParts.push(`Core Problem: ${spec.coreProblem}`);
1995
+ if (spec.featureList && Array.isArray(spec.featureList)) {
1996
+ summaryParts.push("\nKey Features & Nuances:");
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)}`);
1999
+ });
2000
+ }
2001
+ }
2002
+ summaryParts.push("\n=== RIGSTATE TOOLING GUIDELINES ===");
2003
+ summaryParts.push("You have access to specialized MCP tools. USE THEM TO SUCCEED:");
2004
+ summaryParts.push("1. NEVER guess about architecture. Use `query_brain` to search project documentation.");
2005
+ summaryParts.push("2. BEFORE coding, check `get_learned_instructions` to see if you have been corrected before.");
2006
+ summaryParts.push("3. When finishing a task, ALWAYS update the roadmap using `update_roadmap`.");
2007
+ summaryParts.push("4. If you discover a reusable pattern, submit it with `submit_curator_signal`.");
2008
+ summaryParts.push("5. For large refactors, use `run_architecture_audit` to check against rules.");
2009
+ summaryParts.push("6. Store major decisions using `save_decision` (ADR).");
2010
+ summaryParts.push("\n=== RECENT ACTIVITY DIGEST ===");
2011
+ if (agentTasks && agentTasks.length > 0) {
2012
+ summaryParts.push("\nLatest AI Executions:");
2013
+ agentTasks.forEach((t) => {
2014
+ const time = t.completed_at ? new Date(t.completed_at).toLocaleString() : "Recently";
2015
+ summaryParts.push(`- [${time}] ${t.roadmap_title || "Task"}: ${t.execution_summary?.substring(0, 100) || "Completed"}`);
2016
+ });
2017
+ }
2018
+ if (roadmapItems && roadmapItems.length > 0) {
2019
+ summaryParts.push("\nRoadmap Updates:");
2020
+ roadmapItems.forEach((i) => {
2021
+ summaryParts.push(`- ${i.title} is now ${i.status}`);
2022
+ });
2023
+ }
2024
+ return summaryParts.join("\n") || "No project context available.";
2025
+ }
2026
+
1898
2027
  // src/tools/get-project-context.ts
1899
2028
  registry.register({
1900
2029
  name: "get_project_context",
@@ -1940,10 +2069,11 @@ async function getProjectContext(supabase, userId, projectId) {
1940
2069
  created_at: projectRow.created_at,
1941
2070
  last_indexed_at: projectRow.last_indexed_at,
1942
2071
  detected_stack: projectRow.detected_stack,
1943
- repository_tree: projectRow.repository_tree
2072
+ repository_tree: projectRow.repository_tree,
2073
+ functional_spec: projectRow.functional_spec
1944
2074
  };
1945
2075
  const stackDef = projectRow.architectural_dna?.stack_definition;
1946
- const { data: allChunks, error: chunksError } = await supabase.rpc("get_roadmap_chunks_secure", {
2076
+ const { data: allChunks } = await supabase.rpc("get_roadmap_chunks_secure", {
1947
2077
  p_project_id: projectId,
1948
2078
  p_user_id: userId
1949
2079
  });
@@ -1986,67 +2116,17 @@ async function getProjectContext(supabase, userId, projectId) {
1986
2116
  project.repository_tree.filter((t) => t.type === "tree" && !t.path.includes("/")).map((t) => t.path)
1987
2117
  )].slice(0, 10);
1988
2118
  }
1989
- const summaryParts = [];
1990
- summaryParts.push(`Project Type: ${project.project_type?.toUpperCase() || "UNKNOWN"}`);
1991
- if (stackDef) {
1992
- summaryParts.push("\n=== ACTIVE MISSION PARAMETERS ===");
1993
- if (activeTask) {
1994
- summaryParts.push(`\u26A0\uFE0F CURRENT OBJECTIVE: T-${activeTask.step_number}: ${activeTask.title}`);
1995
- summaryParts.push(` Role: ${activeTask.role || "Developer"}`);
1996
- if (activeTask.instruction_set) {
1997
- summaryParts.push(` Instructions: ${activeTask.instruction_set.substring(0, 200)}...`);
1998
- }
1999
- summaryParts.push(" ACTION: Focus ALL coding efforts on completing this task.");
2000
- } else if (nextTask) {
2001
- summaryParts.push(`\u23F8 SYSTEM IDLE (Waiting for command)`);
2002
- summaryParts.push(` Suggested Next Mission: T-${nextTask.step_number}: ${nextTask.title}`);
2003
- summaryParts.push(` ACTION: Ask the user "Shall we start T-${nextTask.step_number}?"`);
2004
- } else {
2005
- summaryParts.push("\u2705 ALL MISSIONS COMPLETE. Awaiting new roadmap items.");
2006
- }
2007
- summaryParts.push("\n=== AI BEHAVIORAL INSTRUCTIONS ===");
2008
- if (activeTask) {
2009
- summaryParts.push(`1. FOCUS: The user is working on T-${activeTask.step_number}. Help them complete it.`);
2010
- summaryParts.push(`2. COMPLIANCE: Ensure all code follows project standards.`);
2011
- } else if (nextTask) {
2012
- summaryParts.push(`1. NUDGE: No active task found. Suggest starting T-${nextTask.step_number} (${nextTask.title}).`);
2013
- summaryParts.push(`2. PROACTIVE: Instead of asking "How can I help?", ask "Shall we start on T-${nextTask.step_number}?"`);
2014
- }
2015
- summaryParts.push("\n=== CURRENT STACK ===");
2016
- if (stackDef.frontend) summaryParts.push(`Frontend: ${stackDef.frontend.framework} (${stackDef.frontend.language})`);
2017
- if (stackDef.backend) summaryParts.push(`Backend: ${stackDef.backend.service} (${stackDef.backend.database})`);
2018
- if (stackDef.styling) summaryParts.push(`Styling: ${stackDef.styling.framework} ${stackDef.styling.library || ""}`);
2019
- if (stackDef.hosting) summaryParts.push(`Infrastructure: ${stackDef.hosting.provider}`);
2020
- } else {
2021
- if (techStack.framework) summaryParts.push(`Framework: ${techStack.framework}`);
2022
- if (techStack.orm) summaryParts.push(`ORM: ${techStack.orm}`);
2023
- }
2024
- if (project.description) {
2025
- summaryParts.push(`
2026
- Description: ${project.description}`);
2027
- }
2028
- summaryParts.push("\n=== RIGSTATE TOOLING GUIDELINES ===");
2029
- summaryParts.push("You have access to specialized MCP tools. USE THEM TO SUCCEED:");
2030
- summaryParts.push("1. NEVER guess about architecture. Use `query_brain` to search project documentation.");
2031
- summaryParts.push("2. BEFORE coding, check `get_learned_instructions` to see if you have been corrected before.");
2032
- summaryParts.push("3. When finishing a task, ALWAYS update the roadmap using `update_roadmap`.");
2033
- summaryParts.push("4. If you discover a reusable pattern, submit it with `submit_curator_signal`.");
2034
- summaryParts.push("5. For large refactors, use `run_architecture_audit` to check against rules.");
2035
- summaryParts.push("6. Store major decisions using `save_decision` (ADR).");
2036
- summaryParts.push("\n=== RECENT ACTIVITY DIGEST ===");
2037
- if (agentTasks && agentTasks.length > 0) {
2038
- summaryParts.push("\nLatest AI Executions:");
2039
- agentTasks.forEach((t) => {
2040
- const time = t.completed_at ? new Date(t.completed_at).toLocaleString() : "Recently";
2041
- summaryParts.push(`- [${time}] ${t.roadmap_title || "Task"}: ${t.execution_summary || "Completed"}`);
2042
- });
2043
- }
2044
- if (roadmapItems && roadmapItems.length > 0) {
2045
- summaryParts.push("\nRoadmap Updates:");
2046
- roadmapItems.forEach((i) => {
2047
- summaryParts.push(`- ${i.title} is now ${i.status}`);
2048
- });
2049
- }
2119
+ const summary = await buildProjectSummary(
2120
+ project,
2121
+ techStack,
2122
+ activeTask,
2123
+ nextTask,
2124
+ agentTasks || [],
2125
+ roadmapItems || [],
2126
+ stackDef,
2127
+ supabase,
2128
+ userId
2129
+ );
2050
2130
  const response = {
2051
2131
  project: {
2052
2132
  id: project.id,
@@ -2057,7 +2137,7 @@ Description: ${project.description}`);
2057
2137
  lastIndexedAt: project.last_indexed_at
2058
2138
  },
2059
2139
  techStack,
2060
- summary: summaryParts.join("\n") || "No project context available."
2140
+ summary
2061
2141
  };
2062
2142
  try {
2063
2143
  const curatorContext = await injectGlobalContext(supabase, userId, {
@@ -2126,32 +2206,25 @@ async function generateQueryEmbedding(query) {
2126
2206
  }
2127
2207
  }
2128
2208
  async function queryBrain(supabase, userId, projectId, query, limit = 8, threshold = 0.5) {
2129
- const { data: project, error: projectError } = await supabase.from("projects").select("id").eq("id", projectId).eq("owner_id", userId).single();
2130
- if (projectError || !project) {
2209
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
2210
+ p_project_id: projectId,
2211
+ p_user_id: userId
2212
+ });
2213
+ if (accessError || !hasAccess) {
2131
2214
  throw new Error("Project not found or access denied");
2132
2215
  }
2133
2216
  const embedding = await generateQueryEmbedding(query);
2134
2217
  let memories = [];
2135
- const { data: searchResults, error: searchError } = await supabase.rpc("hybrid_search_memories", {
2218
+ const { data: searchResults, error: searchError } = await supabase.rpc("query_project_brain_secure", {
2136
2219
  p_project_id: projectId,
2220
+ p_user_id: userId,
2137
2221
  p_query: query,
2138
- p_embedding: embedding || null,
2139
- p_limit: limit,
2140
- p_similarity_threshold: threshold || 0.1
2222
+ p_limit: limit
2141
2223
  });
2142
- if (searchError) {
2143
- 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);
2144
- if (recentMemories) {
2145
- memories = recentMemories.map((m) => ({
2146
- id: m.id,
2147
- content: m.content,
2148
- category: m.category || "general",
2149
- tags: m.tags || [],
2150
- netVotes: m.importance || 0,
2151
- createdAt: m.created_at
2152
- }));
2153
- }
2154
- } else if (searchResults) {
2224
+ if (searchError || !searchResults) {
2225
+ console.error("Brain query failed:", searchError);
2226
+ memories = [];
2227
+ } else {
2155
2228
  memories = searchResults.map((m) => ({
2156
2229
  id: m.id,
2157
2230
  content: m.content,
@@ -2163,8 +2236,15 @@ async function queryBrain(supabase, userId, projectId, query, limit = 8, thresho
2163
2236
  }
2164
2237
  let relevantFeatures = [];
2165
2238
  try {
2166
- const { data: features } = await supabase.from("project_features").select("name, status, description").eq("project_id", projectId).or(`name.ilike.%${query}%,description.ilike.%${query}%`).limit(3);
2167
- if (features) relevantFeatures = features;
2239
+ const { data: projectRow } = await supabase.rpc("get_project_context_secure", {
2240
+ p_project_id: projectId,
2241
+ p_user_id: userId
2242
+ }).single();
2243
+ if (projectRow?.functional_spec) {
2244
+ const spec = typeof projectRow.functional_spec === "string" ? JSON.parse(projectRow.functional_spec) : projectRow.functional_spec;
2245
+ const features = spec.featureList || spec.features || [];
2246
+ relevantFeatures = features.filter((f) => (f.name || f.title)?.toLowerCase().includes(query.toLowerCase())).slice(0, 3);
2247
+ }
2168
2248
  } catch (e) {
2169
2249
  console.warn("Feature fetch failed in brain query", e);
2170
2250
  }
@@ -2174,7 +2254,7 @@ async function queryBrain(supabase, userId, projectId, query, limit = 8, thresho
2174
2254
  const category = m.category ? m.category.toUpperCase() : "GENERAL";
2175
2255
  return `- [${category}]${tagStr}${voteIndicator}: ${m.content}`;
2176
2256
  });
2177
- const searchType = embedding ? "TRIPLE-HYBRID (Vector + FTS + Fuzzy)" : "HYBRID (FTS + Fuzzy)";
2257
+ const searchType = embedding ? "TRIPLE-HYBRID (Vector + FTS + Fuzzy)" : "SECURE-GATEWAY (Fuzzy)";
2178
2258
  let formatted = `=== PROJECT BRAIN: RELEVANT MEMORIES ===
2179
2259
  Search Mode: ${searchType}
2180
2260
  Query: "${query}"`;
@@ -2182,7 +2262,7 @@ Query: "${query}"`;
2182
2262
  formatted += `
2183
2263
 
2184
2264
  === RELATED FEATURES ===
2185
- ` + relevantFeatures.map((f) => `- ${f.name} [${f.status}]`).join("\n");
2265
+ ` + relevantFeatures.map((f) => `- ${f.name || f.title} [${f.status || "Active"}]`).join("\n");
2186
2266
  }
2187
2267
  formatted += `
2188
2268
 
@@ -2320,10 +2400,14 @@ High-importance memory for architectural decisions.`,
2320
2400
  }
2321
2401
  });
2322
2402
  async function saveDecision(supabase, userId, projectId, title, decision, rationale, category = "decision", tags = []) {
2323
- const { data: project, error: projectError } = await supabase.from("projects").select("id, name").eq("id", projectId).eq("owner_id", userId).single();
2324
- if (projectError || !project) {
2403
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
2404
+ p_project_id: projectId,
2405
+ p_user_id: userId
2406
+ });
2407
+ if (accessError || !hasAccess) {
2325
2408
  throw new Error("Project not found or access denied");
2326
2409
  }
2410
+ const { data: project } = await supabase.from("projects").select("name").eq("id", projectId).single();
2327
2411
  const contentParts = [`# ${title}`, "", decision];
2328
2412
  if (rationale) {
2329
2413
  contentParts.push("", "## Rationale", rationale);
@@ -2354,7 +2438,7 @@ async function saveDecision(supabase, userId, projectId, title, decision, ration
2354
2438
  return {
2355
2439
  success: true,
2356
2440
  memoryId: memory.id,
2357
- message: `\u2705 Decision "${title}" saved to project "${project.name}" with importance 9/10`
2441
+ message: `\u2705 Decision "${title}" saved to project "${project?.name || projectId}" with importance 9/10`
2358
2442
  };
2359
2443
  }
2360
2444
 
@@ -2379,10 +2463,14 @@ Ideas can later be reviewed, analyzed, and promoted to features.`,
2379
2463
  }
2380
2464
  });
2381
2465
  async function submitIdea(supabase, userId, projectId, title, description, category = "feature", tags = []) {
2382
- const { data: project, error: projectError } = await supabase.from("projects").select("id, name").eq("id", projectId).eq("owner_id", userId).single();
2383
- if (projectError || !project) {
2466
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
2467
+ p_project_id: projectId,
2468
+ p_user_id: userId
2469
+ });
2470
+ if (accessError || !hasAccess) {
2384
2471
  throw new Error("Project not found or access denied");
2385
2472
  }
2473
+ const { data: project } = await supabase.from("projects").select("name").eq("id", projectId).single();
2386
2474
  const { data: idea, error: insertError } = await supabase.from("saved_ideas").insert({
2387
2475
  project_id: projectId,
2388
2476
  title,
@@ -2408,7 +2496,7 @@ async function submitIdea(supabase, userId, projectId, title, description, categ
2408
2496
  return {
2409
2497
  success: true,
2410
2498
  ideaId: idea.id,
2411
- message: `\u{1F4A1} Idea "${title}" submitted to Idea Lab for project "${project.name}". Status: Draft (awaiting review)`
2499
+ message: `\u{1F4A1} Idea "${title}" submitted to Idea Lab for project "${project?.name || projectId}". Status: Draft (awaiting review)`
2412
2500
  };
2413
2501
  }
2414
2502
 
@@ -2432,10 +2520,14 @@ Can search by chunk ID or by title.`,
2432
2520
  }
2433
2521
  });
2434
2522
  async function updateRoadmap(supabase, userId, projectId, status, chunkId, title) {
2435
- const { data: project, error: projectError } = await supabase.from("projects").select("id, name").eq("id", projectId).eq("owner_id", userId).single();
2436
- if (projectError || !project) {
2523
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
2524
+ p_project_id: projectId,
2525
+ p_user_id: userId
2526
+ });
2527
+ if (accessError || !hasAccess) {
2437
2528
  throw new Error("Project not found or access denied");
2438
2529
  }
2530
+ const { data: project } = await supabase.from("projects").select("name").eq("id", projectId).single();
2439
2531
  let targetChunk = null;
2440
2532
  if (chunkId) {
2441
2533
  const { data, error } = await supabase.from("roadmap_chunks").select("id, title, status").eq("id", chunkId).eq("project_id", projectId).single();
@@ -2564,8 +2656,11 @@ Returns violations or "Pass" status.`,
2564
2656
  }
2565
2657
  });
2566
2658
  async function runArchitectureAudit(supabase, userId, projectId, filePath, content) {
2567
- const { data: project, error: projectError } = await supabase.from("projects").select("id, name").eq("id", projectId).eq("owner_id", userId).single();
2568
- if (projectError || !project) {
2659
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
2660
+ p_project_id: projectId,
2661
+ p_user_id: userId
2662
+ });
2663
+ if (accessError || !hasAccess) {
2569
2664
  throw new Error("Project not found or access denied");
2570
2665
  }
2571
2666
  const violations = [];
@@ -2656,7 +2751,7 @@ registry.register({
2656
2751
  based on project context and user settings.`,
2657
2752
  schema: GenerateCursorRulesInputSchema,
2658
2753
  handler: async (args, context) => {
2659
- const result = await syncIdeRules(context.supabase, args.projectId);
2754
+ const result = await syncIdeRules(context.supabase, context.userId, args.projectId);
2660
2755
  let responseText = `FileName: ${result.fileName}
2661
2756
 
2662
2757
  Content:
@@ -2674,10 +2769,17 @@ Note: Please ensure these modular files are also updated if your IDE is followin
2674
2769
  return { content: [{ type: "text", text: responseText }] };
2675
2770
  }
2676
2771
  });
2677
- async function syncIdeRules(supabase, projectId) {
2772
+ async function syncIdeRules(supabase, userId, projectId) {
2773
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
2774
+ p_project_id: projectId,
2775
+ p_user_id: userId
2776
+ });
2777
+ if (accessError || !hasAccess) {
2778
+ throw new Error("Project not found or access denied");
2779
+ }
2678
2780
  const { data: project, error: projectError } = await supabase.from("projects").select("*").eq("id", projectId).single();
2679
2781
  if (projectError || !project) {
2680
- throw new Error(`Project ${projectId} not found.`);
2782
+ throw new Error(`Project ${projectId} details not found.`);
2681
2783
  }
2682
2784
  const ide = project.preferred_ide || "cursor";
2683
2785
  const [stack, roadmapRes, legacyStats, activeAgents, dbMetadataRes] = await Promise.all([
@@ -2724,25 +2826,30 @@ var listFeaturesTool = {
2724
2826
  Useful for understanding the strategic context and major milestones.`,
2725
2827
  schema: InputSchema,
2726
2828
  handler: async ({ projectId }, { supabase, userId }) => {
2727
- const { data: project, error: projectError } = await supabase.from("projects").select("id, functional_spec").eq("id", projectId).eq("owner_id", userId).single();
2728
- if (projectError || !project) {
2729
- throw new Error("Project not found or access denied");
2829
+ const { data: projectRow, error: projectError } = await supabase.rpc("get_project_context_secure", {
2830
+ p_project_id: projectId,
2831
+ p_user_id: userId
2832
+ }).single();
2833
+ if (projectError || !projectRow) {
2834
+ throw new Error("Project details not found or access denied");
2730
2835
  }
2731
- 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 });
2836
+ const { data: dbFeatures, error: dbError } = await supabase.rpc("get_project_features_secure", {
2837
+ p_project_id: projectId,
2838
+ p_user_id: userId
2839
+ });
2732
2840
  let featuresList = [];
2733
2841
  let source = "DB";
2734
2842
  if (!dbError && dbFeatures && dbFeatures.length > 0) {
2735
2843
  featuresList = dbFeatures.map((f) => ({
2736
2844
  ...f,
2737
2845
  title: f.name
2738
- // Map back to title specifically for uniform handling below
2739
2846
  }));
2740
2847
  } else {
2741
2848
  source = "FALLBACK_SPEC";
2742
- console.error(`[WARN] Project ${projectId}: 'project_features' empty or missing. Falling back to 'functional_spec'.`);
2743
- const spec = project.functional_spec;
2744
- if (spec && typeof spec === "object" && Array.isArray(spec.features)) {
2745
- featuresList = spec.features.map((f) => ({
2849
+ const spec = projectRow.functional_spec;
2850
+ const features = spec?.featureList || spec?.features;
2851
+ if (Array.isArray(features)) {
2852
+ featuresList = features.map((f) => ({
2746
2853
  id: "legacy",
2747
2854
  title: f.name || f.title,
2748
2855
  description: f.description,
@@ -2810,7 +2917,12 @@ async function listRoadmapTasks(supabase, userId, projectId) {
2810
2917
  priority: t.priority,
2811
2918
  status: t.status,
2812
2919
  step_number: t.step_number,
2813
- prompt_content: t.prompt_content
2920
+ prompt_content: t.prompt_content,
2921
+ architectural_brief: t.architectural_brief,
2922
+ context_summary: t.context_summary,
2923
+ metadata: t.metadata,
2924
+ checklist: t.checklist,
2925
+ tags: t.tags
2814
2926
  })),
2815
2927
  formatted
2816
2928
  };
@@ -2865,7 +2977,11 @@ async function getNextRoadmapStep(supabase, userId, projectId, currentStepId) {
2865
2977
  };
2866
2978
  }
2867
2979
  return {
2868
- nextStep,
2980
+ nextStep: {
2981
+ ...nextStep,
2982
+ architectural_brief: nextStep.architectural_brief,
2983
+ context_summary: nextStep.context_summary
2984
+ },
2869
2985
  message: `Next step found: [Step ${nextStep.step_number}] ${nextStep.title}`
2870
2986
  };
2871
2987
  }
@@ -2879,6 +2995,7 @@ registry.register({
2879
2995
  handler: async (args, context) => {
2880
2996
  const result = await checkRulesSync(
2881
2997
  context.supabase,
2998
+ context.userId,
2882
2999
  args.projectId,
2883
3000
  args.currentRulesContent
2884
3001
  );
@@ -2895,7 +3012,7 @@ var SAFETY_CACHE_RULES = `
2895
3012
  4. **Error Handling**: Use try/catch blocks for all external API calls.
2896
3013
  5. **No Blind Deletes**: Never delete data without explicit confirmation or soft-deletes.
2897
3014
  `;
2898
- async function checkRulesSync(supabase, projectId, currentRulesContent) {
3015
+ async function checkRulesSync(supabase, userId, projectId, currentRulesContent) {
2899
3016
  if (!currentRulesContent) {
2900
3017
  return {
2901
3018
  synced: false,
@@ -2915,6 +3032,13 @@ async function checkRulesSync(supabase, projectId, currentRulesContent) {
2915
3032
  };
2916
3033
  }
2917
3034
  try {
3035
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
3036
+ p_project_id: projectId,
3037
+ p_user_id: userId
3038
+ });
3039
+ if (accessError || !hasAccess) {
3040
+ throw new Error("Project not found or access denied");
3041
+ }
2918
3042
  const { data: project, error } = await supabase.from("projects").select("name").eq("id", projectId).single();
2919
3043
  if (error) throw error;
2920
3044
  if (project) {
@@ -2951,7 +3075,14 @@ init_esm_shims();
2951
3075
  init_esm_shims();
2952
3076
  import fs from "fs/promises";
2953
3077
  import path2 from "path";
2954
- async function analyzeDatabasePerformance(supabase, input) {
3078
+ async function analyzeDatabasePerformance(supabase, userId, input) {
3079
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
3080
+ p_project_id: input.projectId,
3081
+ p_user_id: userId
3082
+ });
3083
+ if (accessError || !hasAccess) {
3084
+ throw new Error("Project not found or access denied");
3085
+ }
2955
3086
  const issues = [];
2956
3087
  const { data: rawMetadata, error } = await supabase.rpc("get_table_metadata", {
2957
3088
  p_project_id: input.projectId
@@ -3302,7 +3433,7 @@ registry.register({
3302
3433
  description: `Sven's Tool: Security Shield. Audits the database to ensure Row Level Security (RLS) is enforced.`,
3303
3434
  schema: AuditRlsStatusInputSchema,
3304
3435
  handler: async (args, context) => {
3305
- const result = await auditRlsStatus(context.supabase, args);
3436
+ const result = await auditRlsStatus(context.supabase, context.userId, args);
3306
3437
  return { content: [{ type: "text", text: result.summary || "No summary available" }] };
3307
3438
  }
3308
3439
  });
@@ -3311,11 +3442,18 @@ registry.register({
3311
3442
  description: `Frank's Tool: Security Oracle. Performs a diagnostic security audit against the Fortress Matrix.`,
3312
3443
  schema: AuditSecurityIntegrityInputSchema,
3313
3444
  handler: async (args, context) => {
3314
- const result = await auditSecurityIntegrity(context.supabase, args);
3445
+ const result = await auditSecurityIntegrity(context.supabase, context.userId, args);
3315
3446
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3316
3447
  }
3317
3448
  });
3318
- async function auditRlsStatus(supabase, input) {
3449
+ async function auditRlsStatus(supabase, userId, input) {
3450
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
3451
+ p_project_id: input.projectId,
3452
+ p_user_id: userId
3453
+ });
3454
+ if (accessError || !hasAccess) {
3455
+ throw new Error("Project not found or access denied");
3456
+ }
3319
3457
  try {
3320
3458
  const { data, error } = await supabase.rpc("execute_sql", {
3321
3459
  query: `
@@ -3358,7 +3496,14 @@ async function auditRlsStatus(supabase, input) {
3358
3496
  };
3359
3497
  }
3360
3498
  }
3361
- async function auditSecurityIntegrity(supabase, input) {
3499
+ async function auditSecurityIntegrity(supabase, userId, input) {
3500
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
3501
+ p_project_id: input.projectId,
3502
+ p_user_id: userId
3503
+ });
3504
+ if (accessError || !hasAccess) {
3505
+ throw new Error("Project not found or access denied");
3506
+ }
3362
3507
  const violations = [];
3363
3508
  const { content, filePath } = input;
3364
3509
  const sqlViolation = checkSqlInjection(content);
@@ -3405,15 +3550,22 @@ registry.register({
3405
3550
  Can trigger a SOFT LOCK if critical issues are found.`,
3406
3551
  schema: AuditIntegrityGateInputSchema,
3407
3552
  handler: async (args, context) => {
3408
- const result = await runAuditIntegrityGate(context.supabase, args);
3553
+ const result = await runAuditIntegrityGate(context.supabase, context.userId, args);
3409
3554
  return { content: [{ type: "text", text: result.summary }] };
3410
3555
  }
3411
3556
  });
3412
- async function runAuditIntegrityGate(supabase, input) {
3557
+ async function runAuditIntegrityGate(supabase, userId, input) {
3558
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
3559
+ p_project_id: input.projectId,
3560
+ p_user_id: userId
3561
+ });
3562
+ if (accessError || !hasAccess) {
3563
+ throw new Error("Project not found or access denied");
3564
+ }
3413
3565
  const checks = [];
3414
3566
  let isSoftLocked = false;
3415
3567
  try {
3416
- const rlsResult = await auditRlsStatus(supabase, { projectId: input.projectId });
3568
+ const rlsResult = await auditRlsStatus(supabase, userId, { projectId: input.projectId });
3417
3569
  const unsecuredTables = rlsResult.unsecuredTables || [];
3418
3570
  if (unsecuredTables.length > 0) {
3419
3571
  isSoftLocked = true;
@@ -3442,7 +3594,7 @@ async function runAuditIntegrityGate(supabase, input) {
3442
3594
  try {
3443
3595
  const fs3 = await import("fs/promises");
3444
3596
  const content = await fs3.readFile(path4, "utf-8");
3445
- const securityResult = await auditSecurityIntegrity(supabase, {
3597
+ const securityResult = await auditSecurityIntegrity(supabase, userId, {
3446
3598
  projectId: input.projectId,
3447
3599
  filePath: path4,
3448
3600
  content
@@ -3473,7 +3625,7 @@ async function runAuditIntegrityGate(supabase, input) {
3473
3625
  }
3474
3626
  if (input.filePaths && input.filePaths.length > 0) {
3475
3627
  try {
3476
- const perfResult = await analyzeDatabasePerformance(supabase, {
3628
+ const perfResult = await analyzeDatabasePerformance(supabase, userId, {
3477
3629
  projectId: input.projectId,
3478
3630
  filePaths: input.filePaths
3479
3631
  });
@@ -3532,6 +3684,7 @@ registry.register({
3532
3684
  handler: async (args, context) => {
3533
3685
  const result = await completeRoadmapTask(
3534
3686
  context.supabase,
3687
+ context.userId,
3535
3688
  args.projectId,
3536
3689
  args.summary,
3537
3690
  args.taskId,
@@ -3541,7 +3694,14 @@ registry.register({
3541
3694
  return { content: [{ type: "text", text: result.message }] };
3542
3695
  }
3543
3696
  });
3544
- async function completeRoadmapTask(supabase, projectId, summary, taskId, gitDiff, integrityGate) {
3697
+ async function completeRoadmapTask(supabase, userId, projectId, summary, taskId, gitDiff, integrityGate) {
3698
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
3699
+ p_project_id: projectId,
3700
+ p_user_id: userId
3701
+ });
3702
+ if (accessError || !hasAccess) {
3703
+ throw new Error("Project not found or access denied");
3704
+ }
3545
3705
  let targetTaskId = taskId;
3546
3706
  if (!targetTaskId) {
3547
3707
  const { data: activeTask } = await supabase.from("roadmap_chunks").select("id, title").eq("project_id", projectId).in("status", ["IN_PROGRESS", "ACTIVE"]).order("step_number", { ascending: true }).limit(1).single();
@@ -3653,19 +3813,18 @@ async function saveToProjectBrain(supabase, userId, input) {
3653
3813
  const fullContent = `# ${title}
3654
3814
 
3655
3815
  ${content}`;
3656
- const { data, error } = await supabase.from("project_memories").insert({
3657
- project_id: projectId,
3658
- content: fullContent,
3659
- category: category.toLowerCase(),
3660
- tags,
3661
- importance: category === "DECISION" || category === "ARCHITECTURE" ? 9 : 5,
3662
- is_active: true,
3663
- source_type: "chat_manual"
3664
- }).select("id").single();
3816
+ const { data: memoryId, error } = await supabase.rpc("save_project_memory_secure", {
3817
+ p_project_id: projectId,
3818
+ p_user_id: userId,
3819
+ p_content: fullContent,
3820
+ p_category: category.toLowerCase(),
3821
+ p_tags: tags || [],
3822
+ p_importance: category === "DECISION" || category === "ARCHITECTURE" ? 9 : 5
3823
+ });
3665
3824
  if (error) throw new Error(`Failed to save memory: ${error.message}`);
3666
3825
  return {
3667
3826
  success: true,
3668
- memoryId: data.id,
3827
+ memoryId,
3669
3828
  message: `\u2705 Saved [${category}] "${title}" to Project Brain.`
3670
3829
  };
3671
3830
  }
@@ -3958,8 +4117,18 @@ function interpolateScribePrompt(basePrompt, vars) {
3958
4117
  // src/tools/generate-professional-pdf.ts
3959
4118
  async function generateProfessionalPdf(supabase, userId, projectId, reportType) {
3960
4119
  console.error(`\u{1F58B}\uFE0F The Scribe is preparing a ${reportType} briefing for project ${projectId}...`);
4120
+ const { data: hasAccess, error: accessError } = await supabase.rpc("check_project_access_secure", {
4121
+ p_project_id: projectId,
4122
+ p_user_id: userId
4123
+ });
4124
+ if (accessError || !hasAccess) {
4125
+ throw new Error("Project not found or access denied");
4126
+ }
3961
4127
  const persona = await getScribePersona(supabase);
3962
- const { data: project } = await supabase.from("projects").select("name, description, project_type, detected_stack").eq("id", projectId).single();
4128
+ const { data: project, error: projectError } = await supabase.from("projects").select("name, description, project_type, detected_stack").eq("id", projectId).single();
4129
+ if (projectError || !project) {
4130
+ throw new Error("Project details not found");
4131
+ }
3963
4132
  const projectName = project?.name || "Rigstate Project";
3964
4133
  const projectDescription = project?.description || "A cutting-edge software project built with modern architecture.";
3965
4134
  const projectType = project?.project_type || "Web Application";