@rigstate/mcp 0.4.2 → 0.4.3
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/.agent/skills/client-side-notification-logger/SKILL.md +139 -0
- package/.agent/skills/react-state-counter/SKILL.md +73 -0
- package/.agent/skills/rigstate-evolutionary-refactor/SKILL.md +40 -0
- package/.agent/skills/rigstate-integrity-gate/SKILL.md +55 -0
- package/.agent/skills/rigstate-legacy-renovator/SKILL.md +12 -0
- package/.agent/skills/sec-auth-04/SKILL.md +22 -0
- package/.agent/skills/sec-key-01/SKILL.md +21 -0
- package/.agent/skills/sec-rls-01/SKILL.md +22 -0
- package/.agent/skills/sec-sql-01/SKILL.md +23 -0
- package/.agent/skills/sec-ui-01/SKILL.md +21 -0
- package/.cursor/rules/rigstate-database.mdc +89 -0
- package/.cursor/rules/rigstate-guardian.mdc +43 -0
- package/.cursor/rules/rigstate-identity.mdc +45 -0
- package/.cursor/rules/rigstate-roadmap.mdc +9 -0
- package/.cursor/rules/rigstate-workflow.mdc +323 -0
- package/.cursorrules +402 -0
- package/AGENTS.md +34 -0
- package/dist/index.js +2604 -3067
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/roadmap.json +815 -21
- package/src/index.ts +16 -1765
- package/src/lib/context-engine.ts +85 -0
- package/src/lib/curator/actions/fortress.ts +77 -0
- package/src/lib/curator/actions/query.ts +73 -0
- package/src/lib/curator/actions/stats.ts +70 -0
- package/src/lib/curator/actions/submit.ts +190 -0
- package/src/lib/curator/index.ts +10 -0
- package/src/lib/curator/schemas.ts +37 -0
- package/src/lib/schemas.ts +191 -0
- package/src/lib/types.ts +102 -261
- package/src/server/core.ts +40 -0
- package/src/server/factory.ts +78 -0
- package/src/server/telemetry.ts +122 -0
- package/src/server/types.ts +21 -0
- package/src/tools/analyze-database-performance.ts +157 -0
- package/src/tools/arch-tools.ts +16 -0
- package/src/tools/audit-integrity-gate.ts +166 -0
- package/src/tools/check-rules-sync.ts +20 -0
- package/src/tools/complete-roadmap-task.ts +88 -31
- package/src/tools/curator-tools.ts +74 -0
- package/src/tools/get-latest-decisions.ts +22 -0
- package/src/tools/get-next-roadmap-step.ts +21 -0
- package/src/tools/get-project-context.ts +35 -1
- package/src/tools/index.ts +7 -0
- package/src/tools/list-features.ts +4 -1
- package/src/tools/list-roadmap-tasks.ts +21 -0
- package/src/tools/planning-tools.ts +40 -0
- package/src/tools/query-brain.ts +25 -1
- package/src/tools/run-architecture-audit.ts +23 -0
- package/src/tools/save-decision.ts +26 -0
- package/src/tools/security-checks.ts +241 -0
- package/src/tools/security-tools.ts +88 -18
- package/src/tools/submit-idea.ts +25 -0
- package/src/tools/sync-ide-rules.ts +35 -3
- package/src/tools/teacher-mode.ts +92 -13
- package/src/tools/update-roadmap.ts +24 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { ListToolsRequestSchema, ListResourcesRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { SERVER_NAME, SERVER_VERSION } from './types.js';
|
|
4
|
+
import { registry } from '../lib/tool-registry.js';
|
|
5
|
+
import { getProjectMorals } from '../resources/project-morals.js';
|
|
6
|
+
|
|
7
|
+
// Import tool modules to trigger registration
|
|
8
|
+
import '../tools/curator-tools.js';
|
|
9
|
+
import '../tools/teacher-mode.js';
|
|
10
|
+
import '../tools/get-project-context.js';
|
|
11
|
+
import '../tools/query-brain.js';
|
|
12
|
+
import '../tools/get-latest-decisions.js';
|
|
13
|
+
import '../tools/save-decision.js';
|
|
14
|
+
import '../tools/submit-idea.js';
|
|
15
|
+
import '../tools/update-roadmap.js';
|
|
16
|
+
import '../tools/run-architecture-audit.js';
|
|
17
|
+
import '../tools/sync-ide-rules.ts';
|
|
18
|
+
import '../tools/list-features.js';
|
|
19
|
+
import '../tools/list-roadmap-tasks.js';
|
|
20
|
+
import '../tools/get-next-roadmap-step.js';
|
|
21
|
+
import '../tools/check-rules-sync.js';
|
|
22
|
+
import '../tools/audit-integrity-gate.js';
|
|
23
|
+
import '../tools/complete-roadmap-task.js';
|
|
24
|
+
import '../tools/planning-tools.js';
|
|
25
|
+
import '../tools/security-tools.js';
|
|
26
|
+
import '../tools/arch-tools.js';
|
|
27
|
+
|
|
28
|
+
export const TOOLS = [
|
|
29
|
+
{
|
|
30
|
+
name: 'write_to_file',
|
|
31
|
+
description: 'Guardian Lock: Blocks file writes if RIGSTATE_MODE is SUPERVISOR.',
|
|
32
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: true }
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'replace_file_content',
|
|
36
|
+
description: 'Guardian Lock: Blocks file edits if RIGSTATE_MODE is SUPERVISOR.',
|
|
37
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: true }
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'run_command',
|
|
41
|
+
description: 'Guardian Lock: Blocks commands if RIGSTATE_MODE is SUPERVISOR.',
|
|
42
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: true }
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'get_agent_status',
|
|
46
|
+
description: `Checks the status of the internal Frank Watcher agent.`,
|
|
47
|
+
inputSchema: { type: 'object', properties: {} }
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
export const RESOURCES = [
|
|
53
|
+
{
|
|
54
|
+
uri: 'rigstate://project_morals',
|
|
55
|
+
name: 'Project Morals & Sovereignty',
|
|
56
|
+
mimeType: 'text/markdown',
|
|
57
|
+
description: 'The core ethical and architectural DNA of the project.'
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export function createMcpServer() {
|
|
62
|
+
const server = new Server(
|
|
63
|
+
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
64
|
+
{ capabilities: { tools: {}, resources: {} } }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// List Tools
|
|
68
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
69
|
+
tools: [...TOOLS, ...registry.getTools()]
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
// List Resources
|
|
73
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
74
|
+
resources: RESOURCES
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
return server;
|
|
78
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { WatcherState } from './types.js';
|
|
3
|
+
import { generateProfessionalPdf } from '../tools/generate-professional-pdf.js';
|
|
4
|
+
|
|
5
|
+
export let watcherState: WatcherState = {
|
|
6
|
+
isRunning: false,
|
|
7
|
+
lastCheck: null,
|
|
8
|
+
tasksFound: 0,
|
|
9
|
+
projectId: null
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export async function startFrankWatcher(supabase: SupabaseClient, userId: string) {
|
|
13
|
+
if (watcherState.isRunning) return;
|
|
14
|
+
watcherState.isRunning = true;
|
|
15
|
+
console.error(`🤖 Frank Watcher started for user ${userId}`);
|
|
16
|
+
|
|
17
|
+
const checkTasks = async () => {
|
|
18
|
+
try {
|
|
19
|
+
watcherState.lastCheck = new Date().toISOString();
|
|
20
|
+
|
|
21
|
+
const { data: tasks, error } = await supabase
|
|
22
|
+
.from('agent_bridge')
|
|
23
|
+
.select('*')
|
|
24
|
+
.in('status', ['PENDING', 'APPROVED'])
|
|
25
|
+
.order('created_at', { ascending: true })
|
|
26
|
+
.limit(1);
|
|
27
|
+
|
|
28
|
+
if (error) return;
|
|
29
|
+
|
|
30
|
+
if (tasks && tasks.length > 0) {
|
|
31
|
+
const task = tasks[0];
|
|
32
|
+
watcherState.tasksFound++;
|
|
33
|
+
|
|
34
|
+
// Heartbeat Logic
|
|
35
|
+
if (task.proposal?.startsWith('ping') || (task.task_id === null && !task.proposal?.startsWith('report'))) {
|
|
36
|
+
console.error(`\n⚡ HEARTBEAT: Frank received REAL-TIME PING for project ${task.project_id}. Response: PONG`);
|
|
37
|
+
await supabase.from('agent_bridge').update({
|
|
38
|
+
status: 'COMPLETED',
|
|
39
|
+
summary: 'Pong! Frank is active and listening.',
|
|
40
|
+
updated_at: new Date().toISOString()
|
|
41
|
+
}).eq('id', task.id);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Report Generation Logic
|
|
46
|
+
if (task.proposal?.startsWith('report')) {
|
|
47
|
+
const parts = task.proposal.split(':');
|
|
48
|
+
const signalType = parts[1];
|
|
49
|
+
const reportType = signalType === 'MANIFEST' ? 'SYSTEM_MANIFEST' : 'INVESTOR_REPORT';
|
|
50
|
+
console.error(`\n📄 Frank is generating ${reportType} report...`);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const result = await generateProfessionalPdf(supabase, userId, task.project_id, reportType);
|
|
54
|
+
await supabase.from('agent_bridge').update({
|
|
55
|
+
status: 'COMPLETED',
|
|
56
|
+
summary: `Report generated: ${reportType}.`,
|
|
57
|
+
proposal: JSON.stringify(result.data),
|
|
58
|
+
updated_at: new Date().toISOString()
|
|
59
|
+
}).eq('id', task.id);
|
|
60
|
+
} catch (genError: any) {
|
|
61
|
+
await supabase.from('agent_bridge').update({
|
|
62
|
+
status: 'FAILED',
|
|
63
|
+
summary: `Generation failed: ${genError.message}`,
|
|
64
|
+
updated_at: new Date().toISOString()
|
|
65
|
+
}).eq('id', task.id);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Execution Logic
|
|
71
|
+
if (task.status === 'APPROVED') {
|
|
72
|
+
console.error(`\n🏗️ Worker: EXECUTING approved task: [${task.id}]`);
|
|
73
|
+
await supabase.from('agent_bridge').update({ status: 'EXECUTING', updated_at: new Date().toISOString() }).eq('id', task.id);
|
|
74
|
+
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
76
|
+
|
|
77
|
+
const taskTitle = (task.roadmap_chunks as any)?.title || 'manual objective';
|
|
78
|
+
const technicalSummary = `Processed ${task.task_id || 'manual task'}. Applied architectural alignment.`;
|
|
79
|
+
const humanSummary = `Mission Accomplished for "${taskTitle}". 🚀`;
|
|
80
|
+
|
|
81
|
+
await supabase.from('mission_reports').insert({
|
|
82
|
+
bridge_id: task.id,
|
|
83
|
+
project_id: task.project_id,
|
|
84
|
+
task_id: task.task_id,
|
|
85
|
+
human_summary: humanSummary,
|
|
86
|
+
technical_summary: technicalSummary
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await supabase.from('agent_bridge').update({
|
|
90
|
+
status: 'COMPLETED',
|
|
91
|
+
summary: humanSummary,
|
|
92
|
+
execution_summary: technicalSummary,
|
|
93
|
+
updated_at: new Date().toISOString(),
|
|
94
|
+
completed_at: new Date().toISOString()
|
|
95
|
+
}).eq('id', task.id);
|
|
96
|
+
|
|
97
|
+
if (task.task_id) {
|
|
98
|
+
await supabase.from('roadmap_chunks').update({ status: 'COMPLETED', completed_at: new Date().toISOString() }).eq('id', task.task_id);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Proposal Analysis Logic
|
|
104
|
+
const taskTitle = (task.roadmap_chunks as any)?.title || '';
|
|
105
|
+
let proposal = `Frank (Auto-Mode) has analyzed "${taskTitle}". Ready to proceed.`;
|
|
106
|
+
|
|
107
|
+
await supabase.from('agent_bridge').update({
|
|
108
|
+
status: 'AWAITING_APPROVAL',
|
|
109
|
+
proposal,
|
|
110
|
+
updated_at: new Date().toISOString()
|
|
111
|
+
}).eq('id', task.id);
|
|
112
|
+
}
|
|
113
|
+
} catch (e) { }
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const channel = supabase.channel('frank-watcher').on('postgres_changes', { event: '*', schema: 'public', table: 'agent_bridge' }, (payload: any) => {
|
|
117
|
+
if (payload.new.status === 'PENDING' || payload.new.status === 'APPROVED') checkTasks();
|
|
118
|
+
}).subscribe();
|
|
119
|
+
|
|
120
|
+
setInterval(checkTasks, 5000);
|
|
121
|
+
checkTasks();
|
|
122
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
export interface WatcherState {
|
|
4
|
+
isRunning: boolean;
|
|
5
|
+
lastCheck: string | null;
|
|
6
|
+
tasksFound: number;
|
|
7
|
+
projectId: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface McpServerConfig {
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const SERVER_NAME = 'rigstate-mcp';
|
|
16
|
+
export const SERVER_VERSION = '0.5.0'; // Evolutionary Update
|
|
17
|
+
|
|
18
|
+
export interface AuthContext {
|
|
19
|
+
supabase: SupabaseClient;
|
|
20
|
+
userId: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
|
|
2
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
export interface AnalyzeDatabasePerformanceInput {
|
|
7
|
+
projectId: string;
|
|
8
|
+
filePaths: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface PerformanceIssue {
|
|
12
|
+
type: 'MISSING_INDEX' | 'N_PLUS_ONE' | 'UNOPTIMIZED_FILTER' | 'DEAD_TABLE';
|
|
13
|
+
severity: 'HIGH' | 'MEDIUM' | 'LOW';
|
|
14
|
+
file: string;
|
|
15
|
+
line?: number;
|
|
16
|
+
description: string;
|
|
17
|
+
suggestion: string;
|
|
18
|
+
codeSnippet?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface TableMetadata {
|
|
22
|
+
table_name: string;
|
|
23
|
+
indexed_columns: string[];
|
|
24
|
+
foreign_keys: { column: string, target_table: string }[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function analyzeDatabasePerformance(
|
|
28
|
+
supabase: SupabaseClient,
|
|
29
|
+
input: AnalyzeDatabasePerformanceInput
|
|
30
|
+
): Promise<{ issues: PerformanceIssue[], summary: string }> {
|
|
31
|
+
const issues: PerformanceIssue[] = [];
|
|
32
|
+
|
|
33
|
+
// 1. Fetch Database Metadata
|
|
34
|
+
const { data: rawMetadata, error } = await supabase.rpc('get_table_metadata', {
|
|
35
|
+
p_project_id: input.projectId
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (error) {
|
|
39
|
+
throw new Error(`Failed to fetch database metadata: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const metadata: TableMetadata[] = rawMetadata.map((t: any) => ({
|
|
43
|
+
table_name: t.t_name || t.table_name,
|
|
44
|
+
indexed_columns: t.indexed_columns || [],
|
|
45
|
+
foreign_keys: t.foreign_keys || []
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// Map for quick lookup
|
|
49
|
+
const tableMap = new Map<string, TableMetadata>();
|
|
50
|
+
metadata.forEach(t => tableMap.set(t.table_name, t));
|
|
51
|
+
|
|
52
|
+
// 2. Analyze Code Files
|
|
53
|
+
for (const filePath of input.filePaths) {
|
|
54
|
+
try {
|
|
55
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
56
|
+
const lines = content.split('\n');
|
|
57
|
+
|
|
58
|
+
// --- ANALYSIS PATTERNS ---
|
|
59
|
+
|
|
60
|
+
// A. Detect N+1 Danger Zones (Loops with Await)
|
|
61
|
+
// Regex: .map(async ... await ... from('...'))
|
|
62
|
+
// This is complex for regex, using simple line heuristics
|
|
63
|
+
// If line contains 'await supabase' AND is inside a .map() block? Hard to detect scope.
|
|
64
|
+
// Simplified: If we see `await supabase` inside a loop structure in the same snippet?
|
|
65
|
+
// Let's stick to "Single Line Heuristics" for MVP robustness.
|
|
66
|
+
|
|
67
|
+
// B. Detect Unindexed Filters
|
|
68
|
+
// Pattern: .eq('column', val) or .filter('column', ...)
|
|
69
|
+
// We need to know WHICH table is being queried.
|
|
70
|
+
// Look for `from('tableName')` then track subsequent `.eq('col')`?
|
|
71
|
+
// AST would be better, but regex state machine is feasible for simple chains.
|
|
72
|
+
|
|
73
|
+
let currentTable: string | null = null;
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < lines.length; i++) {
|
|
76
|
+
const line = lines[i];
|
|
77
|
+
|
|
78
|
+
// 1. Identify Table Context
|
|
79
|
+
const fromMatch = line.match(/\.from\(['"]([a-zA-Z0-9_]+)['"]\)/);
|
|
80
|
+
if (fromMatch) {
|
|
81
|
+
currentTable = fromMatch[1];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Reset table context on semicolon (end of chain) mostly
|
|
85
|
+
if (line.includes(';')) {
|
|
86
|
+
currentTable = null; // Conservative reset
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 2. Check for Missing Indexes
|
|
90
|
+
if (currentTable) {
|
|
91
|
+
const eqMatch = line.match(/\.(eq|match|not|gt|gte|lt|lte|like|ilike)\(['"]([a-zA-Z0-9_]+)['"]/);
|
|
92
|
+
if (eqMatch) {
|
|
93
|
+
const colName = eqMatch[2];
|
|
94
|
+
const tableMeta = tableMap.get(currentTable);
|
|
95
|
+
|
|
96
|
+
if (tableMeta) {
|
|
97
|
+
// Check if column is indexed
|
|
98
|
+
// Note: Primary keys are usually indexed automatically, but get_table_metadata might typically return explicit indexes.
|
|
99
|
+
// We should check if it's 'id' (usually PK) or in indexed_columns.
|
|
100
|
+
const isIndexed = tableMeta.indexed_columns.includes(colName) || colName === 'id';
|
|
101
|
+
const isForeignKey = tableMeta.foreign_keys.some(fk => fk.column === colName);
|
|
102
|
+
|
|
103
|
+
if (!isIndexed) {
|
|
104
|
+
issues.push({
|
|
105
|
+
type: 'MISSING_INDEX',
|
|
106
|
+
severity: isForeignKey ? 'HIGH' : 'MEDIUM', // Unindexed Foreign Keys are deadly
|
|
107
|
+
file: filePath,
|
|
108
|
+
line: i + 1,
|
|
109
|
+
description: `Query filters on unindexed column '${colName}' in table '${currentTable}'.`,
|
|
110
|
+
suggestion: `Create an index for '${currentTable}(${colName})'.`,
|
|
111
|
+
codeSnippet: line.trim()
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 3. Check for N+1 (Crudely)
|
|
119
|
+
if (line.includes('await supabase') && (line.includes('.map(') || lines[i - 1]?.includes('.map('))) {
|
|
120
|
+
issues.push({
|
|
121
|
+
type: 'N_PLUS_ONE',
|
|
122
|
+
severity: 'HIGH',
|
|
123
|
+
file: filePath,
|
|
124
|
+
line: i + 1,
|
|
125
|
+
description: `Potential N+1 Query detected. Await inside loop/map.`,
|
|
126
|
+
suggestion: `Use Promise.all() or .in() operator instead of looping queries.`,
|
|
127
|
+
codeSnippet: line.trim()
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
} catch (e) {
|
|
133
|
+
console.warn(`Skipping file ${filePath}: ${e}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 3. Summary Report
|
|
138
|
+
const highSev = issues.filter(i => i.severity === 'HIGH').length;
|
|
139
|
+
const medSev = issues.filter(i => i.severity === 'MEDIUM').length;
|
|
140
|
+
|
|
141
|
+
let summary = `## 🔍 Performance Audit Report\n`;
|
|
142
|
+
summary += `**Scanned Files:** ${input.filePaths.length}\n`;
|
|
143
|
+
summary += `**Issues Found:** ${issues.length} (🔴 ${highSev} High, 🟡 ${medSev} Medium)\n\n`;
|
|
144
|
+
|
|
145
|
+
if (issues.length === 0) {
|
|
146
|
+
summary += `✅ **Clean Scan:** No obvious performance bottlenecks detected based on current schema constraints.`;
|
|
147
|
+
} else {
|
|
148
|
+
summary += `### 🚨 Critical Findings\n`;
|
|
149
|
+
issues.filter(i => i.severity === 'HIGH').forEach(issue => {
|
|
150
|
+
summary += `- **${issue.type}** in \`${path.basename(issue.file)}:${issue.line}\`\n`;
|
|
151
|
+
summary += ` - ${issue.description}\n`;
|
|
152
|
+
summary += ` - 💡 *Fix:* ${issue.suggestion}\n`;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { issues, summary };
|
|
157
|
+
}
|
package/src/tools/arch-tools.ts
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { AnalyzeDependencyGraphInput } from '../lib/types.js';
|
|
4
|
+
import { registry } from '../lib/tool-registry.js';
|
|
5
|
+
import { AnalyzeDependencyGraphInputSchema } from '../lib/schemas.js';
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// Tool Registration
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
registry.register({
|
|
12
|
+
name: 'analyze_dependency_graph',
|
|
13
|
+
description: `Einar's Tool: Architecture Integrity Scanner. Scans the codebase for circular dependencies and structural violations.`,
|
|
14
|
+
schema: AnalyzeDependencyGraphInputSchema,
|
|
15
|
+
handler: async (args, context) => {
|
|
16
|
+
const result = await analyzeDependencyGraph(args);
|
|
17
|
+
return { content: [{ type: 'text', text: (result as any).summary || 'Scan complete' }] };
|
|
18
|
+
}
|
|
19
|
+
});
|
|
4
20
|
|
|
5
21
|
/**
|
|
6
22
|
* Einar's Tool: Architecture Integrity Scanner
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
|
|
2
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import { analyzeDatabasePerformance } from './analyze-database-performance.js';
|
|
4
|
+
import { auditRlsStatus, auditSecurityIntegrity } from './security-tools.js';
|
|
5
|
+
import { IntegrityGateResponse, IntegrityCheckResult, AuditIntegrityGateInput } from '../lib/types.js';
|
|
6
|
+
import { registry } from '../lib/tool-registry.js';
|
|
7
|
+
import { AuditIntegrityGateInputSchema } from '../lib/schemas.js';
|
|
8
|
+
|
|
9
|
+
// ============================================
|
|
10
|
+
// Tool Registration
|
|
11
|
+
// ============================================
|
|
12
|
+
|
|
13
|
+
registry.register({
|
|
14
|
+
name: 'audit_integrity_gate',
|
|
15
|
+
description: `Runs the full Integrity Gate (Security + Performance checks).
|
|
16
|
+
Can trigger a SOFT LOCK if critical issues are found.`,
|
|
17
|
+
schema: AuditIntegrityGateInputSchema,
|
|
18
|
+
handler: async (args, context) => {
|
|
19
|
+
const result = await runAuditIntegrityGate(context.supabase, args);
|
|
20
|
+
return { content: [{ type: 'text', text: result.summary }] };
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export async function runAuditIntegrityGate(
|
|
25
|
+
supabase: SupabaseClient,
|
|
26
|
+
input: AuditIntegrityGateInput
|
|
27
|
+
): Promise<IntegrityGateResponse> {
|
|
28
|
+
const checks: IntegrityCheckResult[] = [];
|
|
29
|
+
let isSoftLocked = false;
|
|
30
|
+
|
|
31
|
+
// 1. Run Guardian Security Audit (RLS)
|
|
32
|
+
try {
|
|
33
|
+
const rlsResult = await auditRlsStatus(supabase, { projectId: input.projectId });
|
|
34
|
+
const unsecuredTables = rlsResult.unsecuredTables || [];
|
|
35
|
+
|
|
36
|
+
if (unsecuredTables.length > 0) {
|
|
37
|
+
isSoftLocked = true;
|
|
38
|
+
checks.push({
|
|
39
|
+
check: 'SECURITY',
|
|
40
|
+
status: 'FAIL',
|
|
41
|
+
message: `Found ${unsecuredTables.length} tables without RLS enabled.`,
|
|
42
|
+
details: unsecuredTables
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
checks.push({
|
|
46
|
+
check: 'SECURITY',
|
|
47
|
+
status: 'PASS',
|
|
48
|
+
message: 'All tables have Row Level Security enabled.'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
} catch (e: any) {
|
|
52
|
+
checks.push({
|
|
53
|
+
check: 'SECURITY',
|
|
54
|
+
status: 'WARN',
|
|
55
|
+
message: `Failed to execute RLS audit: ${e.message}`
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 2. Run Fortress Security Matrix (Phase 7)
|
|
60
|
+
if (input.filePaths && input.filePaths.length > 0) {
|
|
61
|
+
for (const path of input.filePaths) {
|
|
62
|
+
try {
|
|
63
|
+
// We need to read the file content here or pass it?
|
|
64
|
+
// auditSecurityIntegrity requires 'content'.
|
|
65
|
+
// But auditIntegrityGate only gets filePaths.
|
|
66
|
+
// WE NEED TO READ THE FILE.
|
|
67
|
+
// Since this runs on the server, we can use fs.
|
|
68
|
+
// BUT we need to be careful about imports in this environment.
|
|
69
|
+
// Ideally, auditSecurityIntegrity should accept a path and read it,
|
|
70
|
+
// but currently it accepts { filePath, content }.
|
|
71
|
+
|
|
72
|
+
// Let's assume we can read via fs if we are in Node environment (which MCP is).
|
|
73
|
+
const fs = await import('fs/promises');
|
|
74
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
75
|
+
|
|
76
|
+
const securityResult = await auditSecurityIntegrity(supabase, {
|
|
77
|
+
projectId: input.projectId,
|
|
78
|
+
filePath: path,
|
|
79
|
+
content: content
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!securityResult.passed) {
|
|
83
|
+
isSoftLocked = true;
|
|
84
|
+
checks.push({
|
|
85
|
+
check: 'FORTRESS_MATRIX',
|
|
86
|
+
status: 'FAIL',
|
|
87
|
+
message: `Fortress Violations in ${path.split('/').pop()}`,
|
|
88
|
+
details: securityResult.violations
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
checks.push({
|
|
92
|
+
check: 'FORTRESS_MATRIX',
|
|
93
|
+
status: 'PASS',
|
|
94
|
+
message: `Fortress Secured: ${path.split('/').pop()}`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
} catch (e: any) {
|
|
99
|
+
checks.push({
|
|
100
|
+
check: 'FORTRESS_MATRIX',
|
|
101
|
+
status: 'WARN',
|
|
102
|
+
message: `Failed to audit ${path}: ${e.message}`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 3. Run Sindre Performance Audit (Indexes & N+1)
|
|
109
|
+
if (input.filePaths && input.filePaths.length > 0) {
|
|
110
|
+
try {
|
|
111
|
+
const perfResult = await analyzeDatabasePerformance(supabase, {
|
|
112
|
+
projectId: input.projectId,
|
|
113
|
+
filePaths: input.filePaths
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const highSeverityIssues = perfResult.issues.filter(i => i.severity === 'HIGH');
|
|
117
|
+
|
|
118
|
+
if (highSeverityIssues.length > 0) {
|
|
119
|
+
isSoftLocked = true;
|
|
120
|
+
checks.push({
|
|
121
|
+
check: 'PERFORMANCE',
|
|
122
|
+
status: 'FAIL',
|
|
123
|
+
message: `Found ${highSeverityIssues.length} Critical Performance Issues (N+1 or Missing Indexes).`,
|
|
124
|
+
details: highSeverityIssues
|
|
125
|
+
});
|
|
126
|
+
} else if (perfResult.issues.length > 0) {
|
|
127
|
+
checks.push({
|
|
128
|
+
check: 'PERFORMANCE',
|
|
129
|
+
status: 'WARN',
|
|
130
|
+
message: `Found ${perfResult.issues.length} non-critical performance hints.`,
|
|
131
|
+
details: perfResult.issues
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
checks.push({
|
|
135
|
+
check: 'PERFORMANCE',
|
|
136
|
+
status: 'PASS',
|
|
137
|
+
message: 'No performance bottlenecks detected in scanned files.'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
} catch (e: any) {
|
|
141
|
+
checks.push({
|
|
142
|
+
check: 'PERFORMANCE',
|
|
143
|
+
status: 'WARN',
|
|
144
|
+
message: `Failed to execute Performance audit: ${e.message}`
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
checks.push({
|
|
149
|
+
check: 'PERFORMANCE',
|
|
150
|
+
status: 'WARN',
|
|
151
|
+
message: 'Skipped performance scan (no file paths provided).'
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 3. Construct Final Decision
|
|
156
|
+
const summary = isSoftLocked
|
|
157
|
+
? `⛔ INTEGRITY GATE: SOFT LOCK ENGAGED. Critical issues found. Override required.`
|
|
158
|
+
: `✅ INTEGRITY GATE: PASSED. System is secure and optimized.`;
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
passed: !isSoftLocked,
|
|
162
|
+
mode: isSoftLocked ? 'SOFT_LOCK' : 'OPEN',
|
|
163
|
+
checks,
|
|
164
|
+
summary
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
|
|
2
2
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
3
3
|
import { CheckRulesSyncResponse } from '../lib/types.js';
|
|
4
|
+
import { registry } from '../lib/tool-registry.js';
|
|
5
|
+
import { CheckRulesSyncInputSchema } from '../lib/schemas.js';
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// Tool Registration
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
registry.register({
|
|
12
|
+
name: 'check_rules_sync',
|
|
13
|
+
description: `Verifies if the IDE rules are present and belong to the correct project.`,
|
|
14
|
+
schema: CheckRulesSyncInputSchema,
|
|
15
|
+
handler: async (args, context) => {
|
|
16
|
+
const result = await checkRulesSync(
|
|
17
|
+
context.supabase,
|
|
18
|
+
args.projectId,
|
|
19
|
+
args.currentRulesContent
|
|
20
|
+
);
|
|
21
|
+
return { content: [{ type: 'text', text: result.message }] };
|
|
22
|
+
}
|
|
23
|
+
});
|
|
4
24
|
|
|
5
25
|
const RIGSTATE_START = "RIGSTATE_START";
|
|
6
26
|
const RIGSTATE_END = "RIGSTATE_END";
|