@rigstate/mcp 0.4.2 → 0.4.4
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 +3 -3
- 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 +23 -1
- 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,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 8.5.5: Context Engine
|
|
3
|
+
*
|
|
4
|
+
* Bridges the gap between the Curator Database and the Agent's working memory.
|
|
5
|
+
* Performs semantic/keyword lookup to inject relevant rules into the context.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
9
|
+
import { queryGlobalAntidotes } from './curator/index.js';
|
|
10
|
+
|
|
11
|
+
export interface ContextInjectionInput {
|
|
12
|
+
frameworks: string[];
|
|
13
|
+
libraries: string[];
|
|
14
|
+
taskDescription?: string; // Optional: for future task-specific lookup
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function injectGlobalContext(
|
|
18
|
+
supabase: SupabaseClient,
|
|
19
|
+
userId: string,
|
|
20
|
+
input: ContextInjectionInput
|
|
21
|
+
): Promise<string> {
|
|
22
|
+
// 1. Identify relevant tags from stack
|
|
23
|
+
const searchTags = [
|
|
24
|
+
...input.frameworks.map(f => f.split(' ')[0].toLowerCase()),
|
|
25
|
+
...input.libraries.map(l => l.split(' ')[0].toLowerCase())
|
|
26
|
+
].filter(Boolean);
|
|
27
|
+
|
|
28
|
+
// 2. Query Curator for Critical/High severity antidotes matching tags
|
|
29
|
+
// We want HIGH trust items (trust_score >= 80) or IMMUTABLE items.
|
|
30
|
+
// Since queryGlobalAntidotes is a bit generic, we might want a more specialized query here
|
|
31
|
+
// for "Active Constraints", but reusing it is a good MVP.
|
|
32
|
+
|
|
33
|
+
// We fetch ALL Fortress Rules (Immutable) regardless of score/tags?
|
|
34
|
+
// Usually Fortress Rules are universal or highly critical.
|
|
35
|
+
|
|
36
|
+
const { data: fortressRules } = await supabase
|
|
37
|
+
.from('global_antidotes')
|
|
38
|
+
.select('title, instruction, slug')
|
|
39
|
+
.eq('is_immutable', true)
|
|
40
|
+
.eq('is_active', true);
|
|
41
|
+
|
|
42
|
+
// Fetch High Trust Contextual Rules
|
|
43
|
+
let contextQuery = supabase
|
|
44
|
+
.from('global_antidotes')
|
|
45
|
+
.select('title, instruction, framework_tags, severity')
|
|
46
|
+
.eq('is_active', true)
|
|
47
|
+
.gte('trust_score', 80) // High trust only
|
|
48
|
+
.eq('is_immutable', false); // Exclude fortress (already fetched)
|
|
49
|
+
|
|
50
|
+
if (searchTags.length > 0) {
|
|
51
|
+
// PG operator for array overlap: framework_tags && searchTags
|
|
52
|
+
contextQuery = contextQuery.overlaps('framework_tags', searchTags);
|
|
53
|
+
} else {
|
|
54
|
+
// If no tags, maybe just generic ones? Or skip?
|
|
55
|
+
// Let's limit to generic if no tags
|
|
56
|
+
contextQuery = contextQuery.is('framework_tags', null);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { data: contextualRules } = await contextQuery.limit(5);
|
|
60
|
+
|
|
61
|
+
// 3. Format Output
|
|
62
|
+
let output = '';
|
|
63
|
+
|
|
64
|
+
if (fortressRules && fortressRules.length > 0) {
|
|
65
|
+
output += `\n\n## 🏰 FORTRESS RULES (NON-NEGOTIABLE)\n`;
|
|
66
|
+
output += `These rules are immutable and must be followed without exception:\n`;
|
|
67
|
+
fortressRules.forEach(rule => {
|
|
68
|
+
output += `- **${rule.title}**: ${rule.instruction}\n`;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (contextualRules && contextualRules.length > 0) {
|
|
73
|
+
output += `\n## 🛡️ INTELLIGENT CONTEXT (High Trust Antidotes)\n`;
|
|
74
|
+
output += `Learned best practices for your stack (${searchTags.join(', ')}):\n`;
|
|
75
|
+
contextualRules.forEach(rule => {
|
|
76
|
+
output += `- [${rule.severity}] **${rule.title}**: ${rule.instruction}\n`;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!output) {
|
|
81
|
+
output = `\n(No specific curator context found for stack: ${searchTags.join(', ')})`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return output;
|
|
85
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { CheckFortressSchema } from '../schemas.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check instruction against fortress rules
|
|
7
|
+
*/
|
|
8
|
+
export async function checkFortress(
|
|
9
|
+
supabase: SupabaseClient,
|
|
10
|
+
userId: string,
|
|
11
|
+
input: z.infer<typeof CheckFortressSchema>
|
|
12
|
+
) {
|
|
13
|
+
// Load fortress rules
|
|
14
|
+
const { data: fortressRules } = await supabase
|
|
15
|
+
.from('global_antidotes')
|
|
16
|
+
.select('slug, title, instruction')
|
|
17
|
+
.eq('is_immutable', true)
|
|
18
|
+
.eq('is_active', true);
|
|
19
|
+
|
|
20
|
+
if (!fortressRules || fortressRules.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
passed: true,
|
|
23
|
+
conflicts: [],
|
|
24
|
+
message: '✅ No fortress rules configured. Instruction passes by default.'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const conflicts: Array<{ rule: string; title: string; explanation: string }> = [];
|
|
29
|
+
const instructionLower = input.instruction.toLowerCase();
|
|
30
|
+
|
|
31
|
+
// Conflict patterns (simplified)
|
|
32
|
+
const conflictPatterns: Record<string, RegExp[]> = {
|
|
33
|
+
rls_required: [/disable\s+r(ow\s+level\s+)?s(ecurity)?/i, /skip\s+rls/i, /bypass\s+rls/i],
|
|
34
|
+
no_client_secrets: [/api\s+keys?\s+in\s+(frontend|client)/i, /hardcode\s+secret/i],
|
|
35
|
+
input_validation: [/skip\s+validation/i, /trust\s+all\s+input/i],
|
|
36
|
+
auth_required: [/skip\s+auth/i, /bypass\s+auth/i, /disable\s+auth/i]
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
for (const rule of fortressRules) {
|
|
40
|
+
const patterns = conflictPatterns[rule.slug];
|
|
41
|
+
if (patterns) {
|
|
42
|
+
for (const pattern of patterns) {
|
|
43
|
+
if (pattern.test(instructionLower)) {
|
|
44
|
+
conflicts.push({
|
|
45
|
+
rule: rule.slug,
|
|
46
|
+
title: rule.title,
|
|
47
|
+
explanation: `Instruction conflicts with fortress rule: ${rule.instruction.substring(0, 100)}...`
|
|
48
|
+
});
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (conflicts.length > 0) {
|
|
56
|
+
return {
|
|
57
|
+
passed: false,
|
|
58
|
+
conflicts,
|
|
59
|
+
message: `❌ FORTRESS CONFLICT DETECTED
|
|
60
|
+
|
|
61
|
+
${conflicts.length} conflict(s) found:
|
|
62
|
+
${conflicts.map((c, i) => `${i + 1}. [${c.rule}] ${c.title}
|
|
63
|
+
${c.explanation}`).join('\n\n')}
|
|
64
|
+
|
|
65
|
+
⚠️ This instruction cannot be added to the registry as it violates immutable security rules.`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
passed: true,
|
|
71
|
+
conflicts: [],
|
|
72
|
+
message: `✅ FORTRESS CHECK PASSED
|
|
73
|
+
|
|
74
|
+
Instruction does not conflict with any fortress rules.
|
|
75
|
+
Checked against ${fortressRules.length} immutable rules.`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { QueryGlobalAntidotesSchema } from '../schemas.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sigrid's Tool: Query Global Antidotes
|
|
7
|
+
* Search and retrieve antidotes from the global registry.
|
|
8
|
+
*/
|
|
9
|
+
export async function queryGlobalAntidotes(
|
|
10
|
+
supabase: SupabaseClient,
|
|
11
|
+
userId: string,
|
|
12
|
+
input: z.infer<typeof QueryGlobalAntidotesSchema>
|
|
13
|
+
) {
|
|
14
|
+
let query = supabase
|
|
15
|
+
.from('global_antidotes')
|
|
16
|
+
.select('id, slug, title, instruction, example, anti_example, category, severity, framework_tags, trust_score, occurrence_count, is_immutable')
|
|
17
|
+
.eq('is_active', true);
|
|
18
|
+
|
|
19
|
+
if (input.categories && input.categories.length > 0) {
|
|
20
|
+
query = query.in('category', input.categories);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (input.severities && input.severities.length > 0) {
|
|
24
|
+
query = query.in('severity', input.severities);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (input.framework_tags && input.framework_tags.length > 0) {
|
|
28
|
+
query = query.overlaps('framework_tags', input.framework_tags);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (input.min_trust_score !== undefined) {
|
|
32
|
+
query = query.gte('trust_score', input.min_trust_score);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (input.search_text) {
|
|
36
|
+
query = query.or(`title.ilike.%${input.search_text}%,instruction.ilike.%${input.search_text}%`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { data, error } = await query
|
|
40
|
+
.order('severity', { ascending: true }) // CRITICAL first
|
|
41
|
+
.order('trust_score', { ascending: false })
|
|
42
|
+
.limit(input.limit || 20);
|
|
43
|
+
|
|
44
|
+
if (error) throw new Error(`Query failed: ${error.message}`);
|
|
45
|
+
|
|
46
|
+
if (!data || data.length === 0) {
|
|
47
|
+
return {
|
|
48
|
+
antidotes: [],
|
|
49
|
+
formatted: `=== GLOBAL ANTIDOTES REGISTRY ===
|
|
50
|
+
No antidotes found matching your criteria.
|
|
51
|
+
================================`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const formatted = data.map((a, i) => {
|
|
56
|
+
const immutableBadge = a.is_immutable ? ' 🏰 FORTRESS' : '';
|
|
57
|
+
const tags = a.framework_tags ? ` [${a.framework_tags.join(', ')}]` : '';
|
|
58
|
+
return `${i + 1}. [${a.severity}] ${a.title}${immutableBadge}
|
|
59
|
+
Category: ${a.category}${tags}
|
|
60
|
+
Trust: ${a.trust_score}/100 | Occurrences: ${a.occurrence_count}
|
|
61
|
+
${a.instruction.substring(0, 200)}${a.instruction.length > 200 ? '...' : ''}`;
|
|
62
|
+
}).join('\n\n');
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
antidotes: data,
|
|
66
|
+
formatted: `=== GLOBAL ANTIDOTES REGISTRY ===
|
|
67
|
+
Found ${data.length} antidotes:
|
|
68
|
+
|
|
69
|
+
${formatted}
|
|
70
|
+
|
|
71
|
+
================================`
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { GetCuratorStatsSchema } from '../schemas.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get Curator Statistics
|
|
7
|
+
*/
|
|
8
|
+
export async function getCuratorStats(
|
|
9
|
+
supabase: SupabaseClient,
|
|
10
|
+
userId: string,
|
|
11
|
+
input: z.infer<typeof GetCuratorStatsSchema>
|
|
12
|
+
) {
|
|
13
|
+
// Get antidote counts
|
|
14
|
+
const { data: antidotes, count: totalCount } = await supabase
|
|
15
|
+
.from('global_antidotes')
|
|
16
|
+
.select('id, category, severity, is_immutable', { count: 'exact' })
|
|
17
|
+
.eq('is_active', true);
|
|
18
|
+
|
|
19
|
+
const fortressCount = antidotes?.filter(a => a.is_immutable).length || 0;
|
|
20
|
+
|
|
21
|
+
// Get quarantine count
|
|
22
|
+
const { count: quarantineCount } = await supabase
|
|
23
|
+
.from('curation_quarantine')
|
|
24
|
+
.select('id', { count: 'exact', head: true })
|
|
25
|
+
.is('decision', null);
|
|
26
|
+
|
|
27
|
+
// Get today's activity
|
|
28
|
+
const today = new Date();
|
|
29
|
+
today.setHours(0, 0, 0, 0);
|
|
30
|
+
const { data: todayActivity } = await supabase
|
|
31
|
+
.from('curation_audit_log')
|
|
32
|
+
.select('action')
|
|
33
|
+
.gte('created_at', today.toISOString());
|
|
34
|
+
|
|
35
|
+
const activityCounts = todayActivity?.reduce((acc: Record<string, number>, log: any) => {
|
|
36
|
+
acc[log.action] = (acc[log.action] || 0) + 1;
|
|
37
|
+
return acc;
|
|
38
|
+
}, {}) || {};
|
|
39
|
+
|
|
40
|
+
// Category distribution
|
|
41
|
+
const categoryDistribution = antidotes?.reduce((acc: Record<string, number>, a) => {
|
|
42
|
+
acc[a.category] = (acc[a.category] || 0) + 1;
|
|
43
|
+
return acc;
|
|
44
|
+
}, {}) || {};
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
stats: {
|
|
48
|
+
total_antidotes: totalCount || 0,
|
|
49
|
+
fortress_rules: fortressCount,
|
|
50
|
+
pending_quarantine: quarantineCount || 0,
|
|
51
|
+
today_approved: activityCounts['AUTO_APPROVED'] || 0,
|
|
52
|
+
today_rejected: activityCounts['AUTO_REJECTED'] || 0,
|
|
53
|
+
today_quarantined: activityCounts['QUARANTINE_ADDED'] || 0,
|
|
54
|
+
categories: categoryDistribution
|
|
55
|
+
},
|
|
56
|
+
formatted: `=== CURATOR REGISTRY STATS ===
|
|
57
|
+
📊 Global Antidotes: ${totalCount || 0}
|
|
58
|
+
🏰 Fortress Rules: ${fortressCount}
|
|
59
|
+
⏳ Pending Review: ${quarantineCount || 0}
|
|
60
|
+
|
|
61
|
+
📅 Today's Activity:
|
|
62
|
+
✅ Approved: ${activityCounts['AUTO_APPROVED'] || 0}
|
|
63
|
+
❌ Rejected: ${activityCounts['AUTO_REJECTED'] || 0}
|
|
64
|
+
⏸️ Quarantined: ${activityCounts['QUARANTINE_ADDED'] || 0}
|
|
65
|
+
|
|
66
|
+
📁 By Category:
|
|
67
|
+
${Object.entries(categoryDistribution).map(([cat, count]) => ` • ${cat}: ${count}`).join('\n')}
|
|
68
|
+
==============================`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { SubmitSignalSchema } from '../schemas.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sigrid's Tool: Submit Signal for Curation
|
|
7
|
+
* Submit a new intelligence signal for processing through the curation pipeline.
|
|
8
|
+
*/
|
|
9
|
+
export async function submitSignal(
|
|
10
|
+
supabase: SupabaseClient,
|
|
11
|
+
userId: string,
|
|
12
|
+
input: z.infer<typeof SubmitSignalSchema>
|
|
13
|
+
) {
|
|
14
|
+
// 1. Verify project ownership
|
|
15
|
+
const { data: project, error: projectError } = await supabase
|
|
16
|
+
.from('projects')
|
|
17
|
+
.select('id')
|
|
18
|
+
.eq('id', input.projectId)
|
|
19
|
+
.eq('owner_id', userId)
|
|
20
|
+
.single();
|
|
21
|
+
|
|
22
|
+
if (projectError || !project) {
|
|
23
|
+
throw new Error('Project not found or access denied');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 2. Simple fingerprint for deduplication check (basic version for MCP)
|
|
27
|
+
const fingerprintBase = `${input.instruction}::${input.category}`.toLowerCase();
|
|
28
|
+
const simpleHash = fingerprintBase.split('').reduce((a, b) => {
|
|
29
|
+
a = ((a << 5) - a) + b.charCodeAt(0);
|
|
30
|
+
return a & a;
|
|
31
|
+
}, 0).toString(16);
|
|
32
|
+
|
|
33
|
+
// 3. Check for existing similar antidotes
|
|
34
|
+
const { data: existing } = await supabase
|
|
35
|
+
.from('global_antidotes')
|
|
36
|
+
.select('id, slug, title')
|
|
37
|
+
.ilike('instruction', `%${input.instruction.substring(0, 100)}%`)
|
|
38
|
+
.eq('is_active', true)
|
|
39
|
+
.limit(1);
|
|
40
|
+
|
|
41
|
+
if (existing && existing.length > 0) {
|
|
42
|
+
return handleReinforcement(supabase, existing[0]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 4. Check against fortress rules
|
|
46
|
+
const fortressResult = await checkFortressConflicts(supabase, userId, input, simpleHash);
|
|
47
|
+
if (!fortressResult.success) {
|
|
48
|
+
return fortressResult;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 5. Submit to Quarantine
|
|
52
|
+
return submitToQuarantine(supabase, userId, input, simpleHash);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Helpers
|
|
56
|
+
async function handleReinforcement(supabase: SupabaseClient, existing: any) {
|
|
57
|
+
// Signal reinforces existing antidote - update last reinforced timestamp
|
|
58
|
+
await supabase
|
|
59
|
+
.from('global_antidotes')
|
|
60
|
+
.update({ last_reinforced_at: new Date().toISOString() })
|
|
61
|
+
.eq('id', existing.id);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
success: true,
|
|
65
|
+
action: 'reinforced',
|
|
66
|
+
message: `✅ Signal reinforces existing antidote: "${existing.title}" (${existing.slug}).
|
|
67
|
+
The occurrence count has been increased, boosting the antidote's authority.`,
|
|
68
|
+
antidote_id: existing.id
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function checkFortressConflicts(
|
|
73
|
+
supabase: SupabaseClient,
|
|
74
|
+
userId: string,
|
|
75
|
+
input: z.infer<typeof SubmitSignalSchema>,
|
|
76
|
+
hash: string
|
|
77
|
+
) {
|
|
78
|
+
const fortressKeywords = {
|
|
79
|
+
rls_required: ['disable rls', 'skip rls', 'bypass rls', 'rls off'],
|
|
80
|
+
no_client_secrets: ['api key in client', 'hardcode secret', 'embed api key'],
|
|
81
|
+
input_validation: ['skip validation', 'trust input', 'no validation'],
|
|
82
|
+
auth_required: ['skip auth', 'bypass auth', 'disable auth']
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const instructionLower = input.instruction.toLowerCase();
|
|
86
|
+
for (const [rule, keywords] of Object.entries(fortressKeywords)) {
|
|
87
|
+
for (const keyword of keywords) {
|
|
88
|
+
if (instructionLower.includes(keyword)) {
|
|
89
|
+
// Log violation
|
|
90
|
+
await supabase.from('fortress_violations').insert({
|
|
91
|
+
fortress_rule_slug: rule,
|
|
92
|
+
violated_by_signal_hash: hash,
|
|
93
|
+
conflict_type: 'DIRECT_CONTRADICTION',
|
|
94
|
+
severity: 'CRITICAL',
|
|
95
|
+
conflicting_instruction: input.instruction,
|
|
96
|
+
source_user_id: userId,
|
|
97
|
+
source_project_id: input.projectId
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
action: 'blocked',
|
|
103
|
+
message: `❌ FORTRESS VIOLATION: Signal conflicts with immutable rule "${rule}".
|
|
104
|
+
This instruction attempts to weaken a core security principle and has been rejected.
|
|
105
|
+
The violation has been logged for security audit.`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { success: true };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function submitToQuarantine(
|
|
114
|
+
supabase: SupabaseClient,
|
|
115
|
+
userId: string,
|
|
116
|
+
input: z.infer<typeof SubmitSignalSchema>,
|
|
117
|
+
hash: string
|
|
118
|
+
) {
|
|
119
|
+
// Calculate basic trust score (simplified)
|
|
120
|
+
let trustScore = 50; // Base score
|
|
121
|
+
if (input.example) trustScore += 10;
|
|
122
|
+
if (input.anti_example) trustScore += 10;
|
|
123
|
+
if (input.instruction.length > 100) trustScore += 5;
|
|
124
|
+
if (input.reasoning) trustScore += 5;
|
|
125
|
+
if (input.category === 'SECURITY' && input.severity === 'CRITICAL') trustScore += 10;
|
|
126
|
+
|
|
127
|
+
// Add to quarantine for human review (signals from MCP always go to quarantine)
|
|
128
|
+
const { data: quarantine, error: quarantineError } = await supabase
|
|
129
|
+
.from('curation_quarantine')
|
|
130
|
+
.insert({
|
|
131
|
+
signal_hash: hash,
|
|
132
|
+
signal_content: {
|
|
133
|
+
title: input.title,
|
|
134
|
+
instruction: input.instruction,
|
|
135
|
+
category: input.category,
|
|
136
|
+
severity: input.severity,
|
|
137
|
+
example: input.example,
|
|
138
|
+
anti_example: input.anti_example,
|
|
139
|
+
framework_tags: input.framework_tags,
|
|
140
|
+
source_type: input.source_type || 'mcp_submission',
|
|
141
|
+
source_context: {
|
|
142
|
+
user_id: userId,
|
|
143
|
+
project_id: input.projectId,
|
|
144
|
+
reasoning: input.reasoning
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
trust_score: trustScore,
|
|
148
|
+
trust_breakdown: {
|
|
149
|
+
final_score: trustScore,
|
|
150
|
+
components: {
|
|
151
|
+
completeness: input.example && input.anti_example ? 25 : 15,
|
|
152
|
+
source_trust: 20,
|
|
153
|
+
semantic_quality: 15,
|
|
154
|
+
reinforcement_bonus: 0
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
reason: 'MCP_SUBMISSION',
|
|
158
|
+
suggested_action: trustScore >= 70 ? 'APPROVE' : trustScore >= 50 ? 'MERGE' : 'REJECT',
|
|
159
|
+
source_user_id: userId,
|
|
160
|
+
source_project_id: input.projectId
|
|
161
|
+
})
|
|
162
|
+
.select('id')
|
|
163
|
+
.single();
|
|
164
|
+
|
|
165
|
+
if (quarantineError) throw new Error(`Failed to submit signal: ${quarantineError.message}`);
|
|
166
|
+
|
|
167
|
+
// Log audit entry
|
|
168
|
+
await supabase.from('curation_audit_log').insert({
|
|
169
|
+
action: 'SIGNAL_SUBMITTED',
|
|
170
|
+
reason: `MCP signal submitted: "${input.title}" (trust: ${trustScore})`,
|
|
171
|
+
actor_type: 'SYSTEM',
|
|
172
|
+
actor_id: userId,
|
|
173
|
+
signal_hash: hash,
|
|
174
|
+
quarantine_id: quarantine?.id,
|
|
175
|
+
metadata: { projectId: input.projectId, category: input.category }
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
success: true,
|
|
180
|
+
action: 'quarantined',
|
|
181
|
+
trust_score: trustScore,
|
|
182
|
+
quarantine_id: quarantine?.id,
|
|
183
|
+
message: `✅ Signal submitted for curation.
|
|
184
|
+
Trust Score: ${trustScore}/100
|
|
185
|
+
Status: QUARANTINED (pending human review)
|
|
186
|
+
Suggested Action: ${trustScore >= 70 ? 'APPROVE' : trustScore >= 50 ? 'MERGE' : 'NEEDS_REVIEW'}
|
|
187
|
+
|
|
188
|
+
Sigrid will process this signal and notify you of the result.`
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// ============================================
|
|
4
|
+
// Input Schemas
|
|
5
|
+
// ============================================
|
|
6
|
+
|
|
7
|
+
export const QueryGlobalAntidotesSchema = z.object({
|
|
8
|
+
categories: z.array(z.string()).optional().describe('Filter by categories (SECURITY, ARCHITECTURE, UX, PERFORMANCE, ACCESSIBILITY, MAINTAINABILITY)'),
|
|
9
|
+
severities: z.array(z.string()).optional().describe('Filter by severity (CRITICAL, HIGH, MEDIUM, LOW)'),
|
|
10
|
+
framework_tags: z.array(z.string()).optional().describe('Filter by framework tags (e.g., ["nextjs", "react", "supabase"])'),
|
|
11
|
+
min_trust_score: z.number().optional().describe('Minimum trust score (0-100)'),
|
|
12
|
+
search_text: z.string().optional().describe('Search in title and instruction'),
|
|
13
|
+
limit: z.number().optional().describe('Max results (default: 20)')
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const SubmitSignalSchema = z.object({
|
|
17
|
+
projectId: z.string().describe('The UUID of the Rigstate project'),
|
|
18
|
+
title: z.string().min(10).max(100).describe('Short, descriptive title (10-100 chars)'),
|
|
19
|
+
instruction: z.string().min(50).max(2000).describe('The canonical instruction (50-2000 chars)'),
|
|
20
|
+
category: z.enum(['SECURITY', 'ARCHITECTURE', 'UX', 'PERFORMANCE', 'ACCESSIBILITY', 'MAINTAINABILITY']).describe('The category of this antidote'),
|
|
21
|
+
severity: z.enum(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']).describe('Severity level'),
|
|
22
|
+
example: z.string().optional().describe('Good example demonstrating the instruction'),
|
|
23
|
+
anti_example: z.string().optional().describe('Bad example showing what NOT to do'),
|
|
24
|
+
framework_tags: z.array(z.string()).optional().describe('Relevant framework tags'),
|
|
25
|
+
reasoning: z.string().optional().describe('Why this signal should be added'),
|
|
26
|
+
source_type: z.string().optional().describe('Internal source type override (e.g. TEACHER_MODE)')
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const GetCuratorStatsSchema = z.object({
|
|
30
|
+
projectId: z.string().optional().describe('Optional project ID for context')
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const CheckFortressSchema = z.object({
|
|
34
|
+
projectId: z.string().describe('The UUID of the Rigstate project'),
|
|
35
|
+
instruction: z.string().describe('The instruction to check against fortress rules'),
|
|
36
|
+
category: z.enum(['SECURITY', 'ARCHITECTURE', 'UX', 'PERFORMANCE', 'ACCESSIBILITY', 'MAINTAINABILITY']).describe('Category of instruction')
|
|
37
|
+
});
|