@rigstate/mcp 0.7.5 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +308 -139
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/arch-analysis.ts +171 -0
- package/src/lib/curator/actions/submit.ts +8 -9
- package/src/lib/project-context-utils.ts +148 -0
- package/src/lib/types-roadmap.ts +43 -0
- package/src/lib/types.ts +41 -25
- package/src/server/types.ts +1 -1
- package/src/tools/analyze-database-performance.ts +12 -0
- package/src/tools/archaeological-scan.ts +37 -214
- package/src/tools/audit-integrity-gate.ts +16 -4
- package/src/tools/check-agent-bridge.ts +11 -0
- package/src/tools/check-rules-sync.ts +13 -0
- package/src/tools/complete-roadmap-task.ts +12 -0
- package/src/tools/generate-professional-pdf.ts +17 -2
- package/src/tools/get-next-roadmap-step.ts +5 -1
- package/src/tools/get-project-context.ts +17 -79
- package/src/tools/list-features.ts +20 -24
- package/src/tools/list-roadmap-tasks.ts +6 -1
- package/src/tools/pending-tasks.ts +22 -0
- package/src/tools/planning-tools.ts +10 -14
- package/src/tools/query-brain.ts +37 -50
- package/src/tools/research-tools.ts +9 -8
- package/src/tools/run-architecture-audit.ts +8 -9
- package/src/tools/save-decision.ts +15 -9
- package/src/tools/security-tools.ts +26 -2
- package/src/tools/submit-idea.ts +15 -9
- package/src/tools/sync-ide-rules.ts +16 -3
- package/src/tools/teacher-mode.ts +6 -7
- package/src/tools/update-roadmap.ts +14 -8
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
|
|
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:
|
|
1279
|
-
|
|
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:
|
|
1565
|
-
|
|
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
|
|
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
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
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
|
|
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:
|
|
2130
|
-
|
|
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("
|
|
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
|
-
|
|
2139
|
-
p_limit: limit,
|
|
2140
|
-
p_similarity_threshold: threshold || 0.1
|
|
2222
|
+
p_limit: limit
|
|
2141
2223
|
});
|
|
2142
|
-
if (searchError) {
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
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:
|
|
2167
|
-
|
|
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)" : "
|
|
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:
|
|
2324
|
-
|
|
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
|
|
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:
|
|
2383
|
-
|
|
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
|
|
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:
|
|
2436
|
-
|
|
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:
|
|
2568
|
-
|
|
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:
|
|
2728
|
-
|
|
2729
|
-
|
|
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.
|
|
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
|
-
|
|
2743
|
-
const
|
|
2744
|
-
if (
|
|
2745
|
-
featuresList =
|
|
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.
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
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
|
|
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";
|