@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.
- package/.env.example +8 -0
- package/README.md +352 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3445 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
- package/roadmap.json +531 -0
- package/src/agents/the-scribe.ts +122 -0
- package/src/index.ts +1792 -0
- package/src/lib/supabase.ts +120 -0
- package/src/lib/tool-registry.ts +134 -0
- package/src/lib/types.ts +415 -0
- package/src/lib/utils.ts +10 -0
- package/src/resources/project-morals.ts +92 -0
- package/src/tools/arch-tools.ts +166 -0
- package/src/tools/archaeological-scan.ts +335 -0
- package/src/tools/check-agent-bridge.ts +169 -0
- package/src/tools/check-rules-sync.ts +85 -0
- package/src/tools/complete-roadmap-task.ts +96 -0
- package/src/tools/generate-professional-pdf.ts +232 -0
- package/src/tools/get-latest-decisions.ts +130 -0
- package/src/tools/get-next-roadmap-step.ts +76 -0
- package/src/tools/get-project-context.ts +163 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/list-features.ts +67 -0
- package/src/tools/list-roadmap-tasks.ts +61 -0
- package/src/tools/pending-tasks.ts +228 -0
- package/src/tools/planning-tools.ts +123 -0
- package/src/tools/query-brain.ts +125 -0
- package/src/tools/research-tools.ts +149 -0
- package/src/tools/run-architecture-audit.ts +203 -0
- package/src/tools/save-decision.ts +77 -0
- package/src/tools/security-tools.ts +82 -0
- package/src/tools/submit-idea.ts +66 -0
- package/src/tools/sync-ide-rules.ts +76 -0
- package/src/tools/teacher-mode.ts +171 -0
- package/src/tools/ui-tools.ts +191 -0
- package/src/tools/update-roadmap.ts +105 -0
- package/tsconfig.json +29 -0
- 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
|
+
}
|