@rigstate/mcp 0.4.2

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.
Files changed (40) hide show
  1. package/.env.example +8 -0
  2. package/README.md +352 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +3445 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +43 -0
  7. package/roadmap.json +531 -0
  8. package/src/agents/the-scribe.ts +122 -0
  9. package/src/index.ts +1792 -0
  10. package/src/lib/supabase.ts +120 -0
  11. package/src/lib/tool-registry.ts +134 -0
  12. package/src/lib/types.ts +415 -0
  13. package/src/lib/utils.ts +10 -0
  14. package/src/resources/project-morals.ts +92 -0
  15. package/src/tools/arch-tools.ts +166 -0
  16. package/src/tools/archaeological-scan.ts +335 -0
  17. package/src/tools/check-agent-bridge.ts +169 -0
  18. package/src/tools/check-rules-sync.ts +85 -0
  19. package/src/tools/complete-roadmap-task.ts +96 -0
  20. package/src/tools/generate-professional-pdf.ts +232 -0
  21. package/src/tools/get-latest-decisions.ts +130 -0
  22. package/src/tools/get-next-roadmap-step.ts +76 -0
  23. package/src/tools/get-project-context.ts +163 -0
  24. package/src/tools/index.ts +17 -0
  25. package/src/tools/list-features.ts +67 -0
  26. package/src/tools/list-roadmap-tasks.ts +61 -0
  27. package/src/tools/pending-tasks.ts +228 -0
  28. package/src/tools/planning-tools.ts +123 -0
  29. package/src/tools/query-brain.ts +125 -0
  30. package/src/tools/research-tools.ts +149 -0
  31. package/src/tools/run-architecture-audit.ts +203 -0
  32. package/src/tools/save-decision.ts +77 -0
  33. package/src/tools/security-tools.ts +82 -0
  34. package/src/tools/submit-idea.ts +66 -0
  35. package/src/tools/sync-ide-rules.ts +76 -0
  36. package/src/tools/teacher-mode.ts +171 -0
  37. package/src/tools/ui-tools.ts +191 -0
  38. package/src/tools/update-roadmap.ts +105 -0
  39. package/tsconfig.json +29 -0
  40. package/tsup.config.ts +16 -0
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Tool: run_architecture_audit
3
+ *
4
+ * Audits code against project memories and architecture rules.
5
+ * Returns violations or "Pass" status.
6
+ */
7
+
8
+ import { SupabaseClient } from '@supabase/supabase-js';
9
+ import type { ArchitectureAuditResponse, AuditViolation } from '../lib/types.js';
10
+
11
+ // Vulnerability patterns for static analysis
12
+ const VULNERABILITY_PATTERNS: {
13
+ type: string;
14
+ severity: 'LOW' | 'MEDIUM' | 'HIGH';
15
+ patterns: RegExp[];
16
+ title: string;
17
+ description: string;
18
+ recommendation: string;
19
+ }[] = [
20
+ {
21
+ type: 'SQL_INJECTION',
22
+ severity: 'HIGH',
23
+ patterns: [
24
+ /\$queryRaw\s*`[^`]*\$\{/gi,
25
+ /execute\s*\(\s*[`'"][^`'"]*\$\{/gi,
26
+ /query\s*\(\s*[`'"][^`'"]*\+/gi,
27
+ ],
28
+ title: 'Potential SQL Injection',
29
+ description: 'String interpolation in raw SQL queries can lead to SQL injection.',
30
+ recommendation: 'Use parameterized queries or prepared statements instead of string interpolation.'
31
+ },
32
+ {
33
+ type: 'XSS',
34
+ severity: 'HIGH',
35
+ patterns: [
36
+ /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:/gi,
37
+ /innerHTML\s*=/gi,
38
+ ],
39
+ title: 'Potential XSS Vulnerability',
40
+ description: 'Using dangerouslySetInnerHTML or innerHTML can expose your app to XSS attacks.',
41
+ recommendation: 'Sanitize HTML content using a library like DOMPurify before rendering.'
42
+ },
43
+ {
44
+ type: 'SECRETS_LEAK',
45
+ severity: 'HIGH',
46
+ patterns: [
47
+ /['"]sk_live_[a-zA-Z0-9]{20,}['"]/gi,
48
+ /['"]sk_test_[a-zA-Z0-9]{20,}['"]/gi,
49
+ /api[_-]?key\s*[:=]\s*['"][^'"]{20,}['"]/gi,
50
+ /password\s*[:=]\s*['"][^'"]+['"]/gi,
51
+ ],
52
+ title: 'Potential Secret Leak',
53
+ description: 'Hardcoded secrets or API keys detected in source code.',
54
+ recommendation: 'Use environment variables for secrets. Never commit credentials to version control.'
55
+ },
56
+ {
57
+ type: 'UNVALIDATED_INPUT',
58
+ severity: 'MEDIUM',
59
+ patterns: [
60
+ /['"]use server['"]\s*[\s\S]*?export\s+async\s+function\s+\w+\s*\([^)]*:\s*any\)/gi,
61
+ ],
62
+ title: 'Unvalidated Server Action Input',
63
+ description: 'Server action accepts untyped input, which may lead to injection attacks.',
64
+ recommendation: 'Add Zod schema validation at the start of your server action.'
65
+ },
66
+ {
67
+ type: 'MISSING_AUTH',
68
+ severity: 'MEDIUM',
69
+ patterns: [
70
+ /export\s+async\s+function\s+(GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{[^}]*(?!auth|session|getUser)/gi,
71
+ ],
72
+ title: 'Potentially Missing Authentication',
73
+ description: 'API route handler may not be checking authentication.',
74
+ recommendation: 'Add authentication check at the start of your API handler.'
75
+ }
76
+ ];
77
+
78
+ function extractLineNumber(content: string, match: RegExpMatchArray): number {
79
+ const upToMatch = content.substring(0, match.index || 0);
80
+ return (upToMatch.match(/\n/g) || []).length + 1;
81
+ }
82
+
83
+ export async function runArchitectureAudit(
84
+ supabase: SupabaseClient,
85
+ userId: string,
86
+ projectId: string,
87
+ filePath: string,
88
+ content: string
89
+ ): Promise<ArchitectureAuditResponse> {
90
+ // First, verify project ownership
91
+ const { data: project, error: projectError } = await supabase
92
+ .from('projects')
93
+ .select('id, name')
94
+ .eq('id', projectId)
95
+ .eq('owner_id', userId)
96
+ .single();
97
+
98
+ if (projectError || !project) {
99
+ throw new Error('Project not found or access denied');
100
+ }
101
+
102
+ const violations: AuditViolation[] = [];
103
+
104
+ // Run static pattern analysis
105
+ for (const pattern of VULNERABILITY_PATTERNS) {
106
+ for (const regex of pattern.patterns) {
107
+ const matches = content.matchAll(new RegExp(regex.source, regex.flags));
108
+ for (const match of matches) {
109
+ const lineNumber = extractLineNumber(content, match);
110
+ violations.push({
111
+ type: pattern.type,
112
+ severity: pattern.severity,
113
+ title: pattern.title,
114
+ description: pattern.description,
115
+ lineNumber,
116
+ recommendation: pattern.recommendation
117
+ });
118
+ }
119
+ }
120
+ }
121
+
122
+ // Fetch project memories for context-aware checks
123
+ const { data: memories } = await supabase
124
+ .from('project_memories')
125
+ .select('content, category, tags')
126
+ .eq('project_id', projectId)
127
+ .eq('is_active', true)
128
+ .in('category', ['constraint', 'architecture', 'tech_stack', 'decision'])
129
+ .order('importance', { ascending: false })
130
+ .limit(10);
131
+
132
+ // Check against project constraints
133
+ if (memories && memories.length > 0) {
134
+ for (const memory of memories) {
135
+ // Check for common constraint patterns
136
+ const lowerContent = content.toLowerCase();
137
+ const lowerMemory = memory.content.toLowerCase();
138
+
139
+ // Check "never use X" patterns
140
+ const neverMatch = lowerMemory.match(/never\s+use\s+(\w+)/i);
141
+ if (neverMatch && lowerContent.includes(neverMatch[1])) {
142
+ violations.push({
143
+ type: 'CONSTRAINT_VIOLATION',
144
+ severity: 'MEDIUM',
145
+ title: 'Project Constraint Violation',
146
+ description: `Code may violate project constraint: "${memory.content.substring(0, 100)}..."`,
147
+ recommendation: 'Review the project brain for architectural constraints.'
148
+ });
149
+ }
150
+
151
+ // Check "always use X" patterns
152
+ const alwaysMatch = lowerMemory.match(/always\s+use\s+(\w+)/i);
153
+ if (alwaysMatch && !lowerContent.includes(alwaysMatch[1])) {
154
+ violations.push({
155
+ type: 'CONSTRAINT_VIOLATION',
156
+ severity: 'LOW',
157
+ title: 'Missing Required Pattern',
158
+ description: `Code may be missing required pattern: "${memory.content.substring(0, 100)}..."`,
159
+ recommendation: 'Check if this file should follow the project standard.'
160
+ });
161
+ }
162
+ }
163
+ }
164
+
165
+ // Calculate score (100 - penalties)
166
+ let score = 100;
167
+ for (const v of violations) {
168
+ if (v.severity === 'HIGH') score -= 25;
169
+ else if (v.severity === 'MEDIUM') score -= 10;
170
+ else score -= 5;
171
+ }
172
+ score = Math.max(0, score);
173
+
174
+ // Build summary
175
+ const passed = violations.length === 0;
176
+ let summary: string;
177
+
178
+ if (passed) {
179
+ summary = `✅ PASSED - No violations found in ${filePath}\nScore: ${score}/100`;
180
+ } else {
181
+ const highCount = violations.filter(v => v.severity === 'HIGH').length;
182
+ const medCount = violations.filter(v => v.severity === 'MEDIUM').length;
183
+ const lowCount = violations.filter(v => v.severity === 'LOW').length;
184
+
185
+ summary = `⚠️ AUDIT FAILED - ${violations.length} violation(s) found in ${filePath}
186
+ Score: ${score}/100
187
+ • HIGH: ${highCount}
188
+ • MEDIUM: ${medCount}
189
+ • LOW: ${lowCount}
190
+
191
+ Violations:
192
+ ${violations.map((v, i) => `${i + 1}. [${v.severity}] ${v.title}${v.lineNumber ? ` (line ${v.lineNumber})` : ''}
193
+ ${v.description}
194
+ → ${v.recommendation}`).join('\n\n')}`;
195
+ }
196
+
197
+ return {
198
+ passed,
199
+ score,
200
+ violations,
201
+ summary
202
+ };
203
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Tool: save_decision
3
+ *
4
+ * Saves a new decision/ADR to the project's brain (project_memories).
5
+ * High-importance memory for architectural decisions.
6
+ */
7
+
8
+ import { SupabaseClient } from '@supabase/supabase-js';
9
+ import type { SaveDecisionResponse } from '../lib/types.js';
10
+
11
+ export async function saveDecision(
12
+ supabase: SupabaseClient,
13
+ userId: string,
14
+ projectId: string,
15
+ title: string,
16
+ decision: string,
17
+ rationale?: string,
18
+ category: string = 'decision',
19
+ tags: string[] = []
20
+ ): Promise<SaveDecisionResponse> {
21
+ // First, verify project ownership
22
+ const { data: project, error: projectError } = await supabase
23
+ .from('projects')
24
+ .select('id, name')
25
+ .eq('id', projectId)
26
+ .eq('owner_id', userId)
27
+ .single();
28
+
29
+ if (projectError || !project) {
30
+ throw new Error('Project not found or access denied');
31
+ }
32
+
33
+ // Build the full content with title, decision, and rationale
34
+ const contentParts = [`# ${title}`, '', decision];
35
+ if (rationale) {
36
+ contentParts.push('', '## Rationale', rationale);
37
+ }
38
+ const fullContent = contentParts.join('\n');
39
+
40
+ // Build summary (truncated version for quick context)
41
+ const summary = decision.length > 150
42
+ ? decision.substring(0, 147) + '...'
43
+ : decision;
44
+
45
+ // Insert the memory with high importance (ADR = 9/10)
46
+ const { data: memory, error: insertError } = await supabase
47
+ .from('project_memories')
48
+ .insert({
49
+ project_id: projectId,
50
+ content: fullContent,
51
+ summary: summary,
52
+ category: category,
53
+ tags: ['ADR', ...tags],
54
+ importance: 9, // High importance for decisions
55
+ source_type: 'mcp', // Track source as MCP
56
+ is_active: true
57
+ })
58
+ .select('id')
59
+ .single();
60
+
61
+ if (insertError) {
62
+ // Handle specific database errors
63
+ if (insertError.code === '23503') {
64
+ throw new Error('Project no longer exists');
65
+ }
66
+ if (insertError.code === '42501') {
67
+ throw new Error('Permission denied: Cannot write to this project');
68
+ }
69
+ throw new Error(`Failed to save decision: ${insertError.message}`);
70
+ }
71
+
72
+ return {
73
+ success: true,
74
+ memoryId: memory.id,
75
+ message: `✅ Decision "${title}" saved to project "${project.name}" with importance 9/10`
76
+ };
77
+ }
@@ -0,0 +1,82 @@
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
+ import { AuditRlsStatusInput } from '../lib/types.js';
3
+
4
+ /**
5
+ * Sven's Tool: Security Shield
6
+ * Audits the database to ensure Row Level Security (RLS) is enforced.
7
+ */
8
+ export async function auditRlsStatus(
9
+ supabase: SupabaseClient,
10
+ input: AuditRlsStatusInput
11
+ ) {
12
+ try {
13
+ // Query pg_tables to check rowsecurity status
14
+ const { data, error } = await supabase.rpc('execute_sql', {
15
+ query: `
16
+ SELECT tablename, rowsecurity
17
+ FROM pg_tables
18
+ WHERE schemaname = 'public'
19
+ ORDER BY tablename;
20
+ `
21
+ });
22
+
23
+ // Fallback if generic execute_sql isn't available (it's often a custom RPC in these setups)
24
+ // If RPC fails, try generic postgres connection?
25
+ // MCP usually connects via HTTP API (PostgREST). PostgREST doesn't expose pg_tables directly unless allowed.
26
+ // HOWEVER, the user instruction implies we CAN run this. "Utfør en SQL-spørring..."
27
+ // If using Supabase JS client, we can't run raw SQL unless we use an RPC wrapper.
28
+ // I will assume an RPC 'execute_sql' exists (common in AI setups) OR I have to trust the instruction
29
+ // implies I have a way.
30
+
31
+ // Alternative: Use the 'mcp_supabase-mcp-server_execute_sql' pattern? No, we ARE the MCP server.
32
+ // If we don't have an RPC, we might struggle.
33
+ // Let's implement a robust check.
34
+
35
+ if (error) {
36
+ // Try direct fetch if exposed, which is rare for system tables
37
+ throw new Error(`Database query failed: ${error.message}`);
38
+ }
39
+
40
+ // Parse result (assuming result comes back as array of objects)
41
+ // If data is null/empty but no error?
42
+ const tables = data as Array<{ tablename: string, rowsecurity: boolean }>;
43
+
44
+ if (!tables) {
45
+ // If RPC returns void or strange shape, try to warn.
46
+ return {
47
+ status: 'ERROR',
48
+ message: 'Could not fetch table list. Ensure execute_sql RPC is available or pg_tables is accessible.'
49
+ };
50
+ }
51
+
52
+ const unsecuredTables = tables.filter(t => !t.rowsecurity);
53
+ const securedTables = tables.filter(t => t.rowsecurity);
54
+
55
+ const score = tables.length > 0
56
+ ? Math.round((securedTables.length / tables.length) * 100)
57
+ : 100;
58
+
59
+ return {
60
+ timestamp: new Date().toISOString(),
61
+ projectId: input.projectId,
62
+ securityScore: score,
63
+ metrics: {
64
+ totalTables: tables.length,
65
+ securedTables: securedTables.length,
66
+ unsecuredTables: unsecuredTables.length
67
+ },
68
+ unsecuredTables: unsecuredTables.map(t => t.tablename),
69
+ status: unsecuredTables.length === 0 ? 'SECURE' : 'VULNERABLE',
70
+ summary: unsecuredTables.length === 0
71
+ ? `PASSED. All ${tables.length} tables have RLS enabled. Sven approves.`
72
+ : `CRITICAL. ${unsecuredTables.length} tables are missing RLS: ${unsecuredTables.map(t => t.tablename).join(', ')}. Immediate action required.`
73
+ };
74
+
75
+ } catch (err: any) {
76
+ // Fallback: If we can't reach the DB
77
+ return {
78
+ status: 'ERROR',
79
+ message: `Sven could not reach the database structure. Error: ${err.message || err}. check connection strings.`
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Tool: submit_idea
3
+ *
4
+ * Submits a new idea to the Idea Lab (saved_ideas table).
5
+ * Ideas can later be reviewed, analyzed, and promoted to features.
6
+ */
7
+
8
+ import { SupabaseClient } from '@supabase/supabase-js';
9
+ import type { SubmitIdeaResponse } from '../lib/types.js';
10
+
11
+ export async function submitIdea(
12
+ supabase: SupabaseClient,
13
+ userId: string,
14
+ projectId: string,
15
+ title: string,
16
+ description: string,
17
+ category: string = 'feature',
18
+ tags: string[] = []
19
+ ): Promise<SubmitIdeaResponse> {
20
+ // First, verify project ownership
21
+ const { data: project, error: projectError } = await supabase
22
+ .from('projects')
23
+ .select('id, name')
24
+ .eq('id', projectId)
25
+ .eq('owner_id', userId)
26
+ .single();
27
+
28
+ if (projectError || !project) {
29
+ throw new Error('Project not found or access denied');
30
+ }
31
+
32
+ // Insert the idea into saved_ideas
33
+ const { data: idea, error: insertError } = await supabase
34
+ .from('saved_ideas')
35
+ .insert({
36
+ project_id: projectId,
37
+ title: title,
38
+ description: description,
39
+ category: category,
40
+ tags: ['mcp', ...tags],
41
+ status: 'draft', // Start as draft for review
42
+ // No session_id since this comes from MCP, not a lab session
43
+ })
44
+ .select('id')
45
+ .single();
46
+
47
+ if (insertError) {
48
+ // Handle specific database errors
49
+ if (insertError.code === '23503') {
50
+ throw new Error('Project no longer exists');
51
+ }
52
+ if (insertError.code === '42501') {
53
+ throw new Error('Permission denied: Cannot write to this project');
54
+ }
55
+ if (insertError.code === '23505') {
56
+ throw new Error('An idea with this title already exists');
57
+ }
58
+ throw new Error(`Failed to submit idea: ${insertError.message}`);
59
+ }
60
+
61
+ return {
62
+ success: true,
63
+ ideaId: idea.id,
64
+ message: `💡 Idea "${title}" submitted to Idea Lab for project "${project.name}". Status: Draft (awaiting review)`
65
+ };
66
+ }
@@ -0,0 +1,76 @@
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
+ import {
3
+ generateRuleContent,
4
+ generateRuleFiles,
5
+ fetchLegacyStats,
6
+ fetchActiveAgents,
7
+ fetchProjectTechStack,
8
+ getFileNameForIDE,
9
+ IDEProvider,
10
+ RuleFile
11
+ } from '@rigstate/rules-engine';
12
+
13
+ /**
14
+ * Sync IDE rules for a project.
15
+ * Uses the centralized rules-engine for consistent generation across all consumers.
16
+ *
17
+ * @param supabase - Supabase client instance
18
+ * @param projectId - The project ID to generate rules for
19
+ * @returns Object with fileName, content, and the new modular files[] array
20
+ */
21
+ export async function syncIdeRules(
22
+ supabase: SupabaseClient,
23
+ projectId: string
24
+ ): Promise<{ fileName: string; content: string; files: RuleFile[] }> {
25
+ // 1. Fetch Project & Preferred IDE
26
+ const { data: project, error: projectError } = await supabase
27
+ .from('projects')
28
+ .select('*')
29
+ .eq('id', projectId)
30
+ .single();
31
+
32
+ if (projectError || !project) {
33
+ throw new Error(`Project ${projectId} not found.`);
34
+ }
35
+
36
+ // 2. Determine IDE (Preference -> Fallback)
37
+ const ide: IDEProvider = (project.preferred_ide as IDEProvider) || 'cursor';
38
+
39
+ // 3. Fetch Context Data (Parallel for speed)
40
+ const [stack, roadmapRes, legacyStats, activeAgents] = await Promise.all([
41
+ fetchProjectTechStack(supabase, projectId),
42
+ supabase
43
+ .from('roadmap_chunks')
44
+ .select('step_number, title, status, sprint_focus, prompt_content, is_legacy')
45
+ .eq('project_id', projectId),
46
+ fetchLegacyStats(supabase, projectId),
47
+ fetchActiveAgents(supabase)
48
+ ]);
49
+
50
+ // 4. Generate Content (Mono-file)
51
+ const content = generateRuleContent(
52
+ { ...project, id: projectId },
53
+ stack,
54
+ roadmapRes.data || [],
55
+ ide,
56
+ legacyStats,
57
+ activeAgents
58
+ );
59
+
60
+ // 5. Generate Modular Files (v3.0)
61
+ const fileResult = generateRuleFiles(
62
+ { ...project, id: projectId },
63
+ stack,
64
+ roadmapRes.data || [],
65
+ ide,
66
+ legacyStats,
67
+ activeAgents
68
+ );
69
+
70
+ return {
71
+ fileName: getFileNameForIDE(ide),
72
+ content,
73
+ files: fileResult.files
74
+ };
75
+ }
76
+
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Rigstate MCP Server - Teacher Mode Tools
3
+ *
4
+ * Allows Frank to send logic corrections and fetch learned behaviors.
5
+ */
6
+
7
+ import { SupabaseClient } from '@supabase/supabase-js';
8
+ import { v4 as uuidv4 } from 'uuid';
9
+
10
+ export interface RefineLogicResponse {
11
+ success: boolean;
12
+ instructionId: string;
13
+ traceId: string;
14
+ message: string;
15
+ }
16
+
17
+ export interface LearnedInstructionsResponse {
18
+ instructions: Array<{
19
+ instruction: string;
20
+ priority: number;
21
+ isGlobal: boolean;
22
+ createdAt: string;
23
+ }>;
24
+ globalInstructions: string[];
25
+ formatted: string;
26
+ }
27
+
28
+ /**
29
+ * Send a logic correction to the Rigstate cloud database
30
+ */
31
+ export async function refineLogic(
32
+ supabase: SupabaseClient,
33
+ userId: string,
34
+ projectId: string,
35
+ originalReasoning: string,
36
+ userCorrection: string,
37
+ scope: 'project' | 'global'
38
+ ): Promise<RefineLogicResponse> {
39
+ // Generate a trace ID for this action
40
+ const traceId = uuidv4();
41
+
42
+ // Verify project access
43
+ const { data: project, error: projectError } = await supabase
44
+ .from('projects')
45
+ .select('id, name')
46
+ .eq('id', projectId)
47
+ .eq('user_id', userId)
48
+ .single();
49
+
50
+ if (projectError || !project) {
51
+ throw new Error(`Project access denied or not found: ${projectId}`);
52
+ }
53
+
54
+ // Save the instruction
55
+ const { data: instruction, error: insertError } = await supabase
56
+ .from('ai_instructions')
57
+ .insert({
58
+ user_id: userId,
59
+ project_id: scope === 'global' ? null : projectId,
60
+ instruction: userCorrection,
61
+ context: originalReasoning,
62
+ is_global: scope === 'global',
63
+ priority: scope === 'global' ? 8 : 6,
64
+ source_log_id: null // MCP actions don't have a log ID
65
+ })
66
+ .select('id')
67
+ .single();
68
+
69
+ if (insertError) {
70
+ throw new Error(`Failed to save instruction: ${insertError.message}`);
71
+ }
72
+
73
+ // Log this MCP action with trace
74
+ await supabase
75
+ .from('ai_activity_log')
76
+ .insert({
77
+ user_id: userId,
78
+ project_id: projectId,
79
+ activity_type: 'mcp_correction',
80
+ summary: `MCP: ${userCorrection.slice(0, 100)}...`,
81
+ intelligence_trace: {
82
+ source: 'mcp',
83
+ traceId,
84
+ scope,
85
+ originalReasoning: originalReasoning.slice(0, 500)
86
+ },
87
+ metadata: { mcp_trace_id: traceId }
88
+ });
89
+
90
+ return {
91
+ success: true,
92
+ instructionId: instruction.id,
93
+ traceId,
94
+ message: `✅ Correction saved! Frank will apply this ${scope === 'global' ? 'globally' : 'to this project'}.`
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Fetch learned behaviors from the database
100
+ */
101
+ export async function getLearnedInstructions(
102
+ supabase: SupabaseClient,
103
+ userId: string,
104
+ projectId?: string
105
+ ): Promise<LearnedInstructionsResponse> {
106
+ // Fetch user-specific instructions
107
+ let query = supabase
108
+ .from('ai_instructions')
109
+ .select('instruction, priority, is_global, created_at')
110
+ .eq('user_id', userId)
111
+ .eq('is_active', true)
112
+ .order('priority', { ascending: false })
113
+ .limit(20);
114
+
115
+ // Filter by project or global only
116
+ if (projectId) {
117
+ query = query.or(`is_global.eq.true,project_id.eq.${projectId}`);
118
+ } else {
119
+ query = query.eq('is_global', true);
120
+ }
121
+
122
+ const { data: userInstructions, error: userError } = await query;
123
+
124
+ if (userError) {
125
+ throw new Error(`Failed to fetch instructions: ${userError.message}`);
126
+ }
127
+
128
+ // Fetch global base instructions (system-wide)
129
+ const { data: globalBase, error: globalError } = await supabase
130
+ .from('global_base_instructions')
131
+ .select('instruction')
132
+ .eq('is_active', true)
133
+ .order('priority', { ascending: false })
134
+ .limit(10);
135
+
136
+ const globalInstructions = (globalBase || []).map(g => g.instruction);
137
+
138
+ // Format for context injection
139
+ const instructions = (userInstructions || []).map(i => ({
140
+ instruction: i.instruction as string,
141
+ priority: i.priority as number,
142
+ isGlobal: i.is_global as boolean,
143
+ createdAt: i.created_at as string
144
+ }));
145
+
146
+ let formatted = '';
147
+
148
+ if (globalInstructions.length > 0) {
149
+ formatted += `## GLOBAL RIGSTATE STANDARDS\n`;
150
+ formatted += globalInstructions.map(g => `- ${g}`).join('\n');
151
+ formatted += '\n\n';
152
+ }
153
+
154
+ if (instructions.length > 0) {
155
+ formatted += `## USER CORRECTIONS & TUNING\n`;
156
+ formatted += instructions.map(i => {
157
+ const scope = i.isGlobal ? '[GLOBAL]' : '[PROJECT]';
158
+ return `- ${scope} ${i.instruction}`;
159
+ }).join('\n');
160
+ }
161
+
162
+ if (!formatted) {
163
+ formatted = 'No learned behaviors yet. Frank will start learning as you provide corrections.';
164
+ }
165
+
166
+ return {
167
+ instructions,
168
+ globalInstructions,
169
+ formatted
170
+ };
171
+ }