@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,241 @@
|
|
|
1
|
+
|
|
2
|
+
export interface SecurityViolation {
|
|
3
|
+
id: string;
|
|
4
|
+
type: string;
|
|
5
|
+
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'FATAL';
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
recommendation: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Checks for SQL Injection Vulnerabilities (SEC-SQL-01)
|
|
13
|
+
*/
|
|
14
|
+
export function checkSqlInjection(content: string): SecurityViolation | null {
|
|
15
|
+
const sqlKeywords = ['from', 'select', 'insert', 'update', 'delete', 'rpc', 'execute', 'query'];
|
|
16
|
+
const hasSqlKeywords = sqlKeywords.some(kw => content.toLowerCase().includes(kw));
|
|
17
|
+
|
|
18
|
+
if (hasSqlKeywords) {
|
|
19
|
+
const interpolationRegex = /`[^`]*\${[^`]*`|['"][^'"]*\$\{[^'"]*['"]/;
|
|
20
|
+
if (interpolationRegex.test(content)) {
|
|
21
|
+
return {
|
|
22
|
+
id: 'SEC-SQL-01',
|
|
23
|
+
type: 'SQL_INJECTION',
|
|
24
|
+
severity: 'HIGH',
|
|
25
|
+
title: 'Potential SQL Injection (Dynamic String)',
|
|
26
|
+
description: 'Detected dynamic string building in a context containing SQL keywords.',
|
|
27
|
+
recommendation: 'Use prepared statements or the project-standard query builder. Never interpolate user input directly.'
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Checks for RLS in Migrations (SEC-RLS-01)
|
|
36
|
+
*/
|
|
37
|
+
export function checkRlsInMigration(filePath: string, content: string): SecurityViolation | null {
|
|
38
|
+
if (filePath.includes('supabase/migrations/') && filePath.endsWith('.sql')) {
|
|
39
|
+
const hasCreateTable = /CREATE\s+TABLE/i.test(content);
|
|
40
|
+
const hasEnableRls = /ENABLE\s+ROW\s+LEVEL\s+SECURITY/i.test(content);
|
|
41
|
+
|
|
42
|
+
if (hasCreateTable && !hasEnableRls) {
|
|
43
|
+
return {
|
|
44
|
+
id: 'SEC-RLS-01',
|
|
45
|
+
type: 'DB_INTEGRITY',
|
|
46
|
+
severity: 'HIGH',
|
|
47
|
+
title: 'Missing Row Level Security (RLS)',
|
|
48
|
+
description: 'Migration creates a table but does not enable RLS.',
|
|
49
|
+
recommendation: 'Add ALTER TABLE "public"."your_table" ENABLE ROW LEVEL SECURITY; for every new table.'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Checks for Hardcoded Secrets (SEC-KEY-01)
|
|
58
|
+
*/
|
|
59
|
+
export function checkHardcodedSecrets(filePath: string, content: string): SecurityViolation[] {
|
|
60
|
+
const violations: SecurityViolation[] = [];
|
|
61
|
+
const keyPatterns = [
|
|
62
|
+
{ pattern: /sk_live_[a-zA-Z0-9]{20,}/, name: 'Stripe Secret Key' },
|
|
63
|
+
{ pattern: /sb_publishable_[a-zA-Z0-9]{20,}/, name: 'Supabase Publishable Key' },
|
|
64
|
+
{ pattern: /AIza[0-9A-Za-z\\-_]{35}/, name: 'Google API Key' },
|
|
65
|
+
{ pattern: /"([^"]*(?:password|secret|key|token)[^"]*)"\s*:\s*"[^"]{10,}"/i, name: 'Generic Secret' }
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (const p of keyPatterns) {
|
|
69
|
+
if (p.pattern.test(content) && !filePath.includes('.env')) {
|
|
70
|
+
violations.push({
|
|
71
|
+
id: 'SEC-KEY-01',
|
|
72
|
+
type: 'SECRET_LEAKAGE',
|
|
73
|
+
severity: 'FATAL',
|
|
74
|
+
title: `Hardcoded Secret Detected (${p.name})`,
|
|
75
|
+
description: `Found a potential secret or API key hardcoded in the source file.`,
|
|
76
|
+
recommendation: 'Move all secrets to environment variables (.env.local) and use process.env.'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return violations;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Checks for XSS Vulnerabilities (SEC-UI-01)
|
|
85
|
+
*/
|
|
86
|
+
export function checkXss(content: string): SecurityViolation | null {
|
|
87
|
+
if (content.includes('dangerouslySetInnerHTML')) {
|
|
88
|
+
return {
|
|
89
|
+
id: 'SEC-UI-01',
|
|
90
|
+
type: 'XSS_VULNERABILITY',
|
|
91
|
+
severity: 'MEDIUM',
|
|
92
|
+
title: 'Dangerous HTML Rendering',
|
|
93
|
+
description: 'Detected use of dangerouslySetInnerHTML which can lead to XSS.',
|
|
94
|
+
recommendation: 'Sanitize content with DOMPurify or use safer rendering methods.'
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Checks for strict identity isolation (SEC-AUTH-04)
|
|
102
|
+
*/
|
|
103
|
+
export function checkIdentityIsolation(filePath: string, content: string): SecurityViolation | null {
|
|
104
|
+
if ((filePath.includes('app/api/') || filePath.includes('actions/')) && (content.includes('supabase.from') || content.includes('db.select'))) {
|
|
105
|
+
const hasOwnerFilter = /\.eq\(['"](owner_id|user_id)['"],/.test(content);
|
|
106
|
+
if (!hasOwnerFilter) {
|
|
107
|
+
return {
|
|
108
|
+
id: 'SEC-AUTH-04',
|
|
109
|
+
type: 'ISOLATION_FAILURE',
|
|
110
|
+
severity: 'HIGH',
|
|
111
|
+
title: 'Missing Identity Filter',
|
|
112
|
+
description: 'Data query lacks a user_id or owner_id filter. Required for multi-tenancy isolation.',
|
|
113
|
+
recommendation: 'Always filter tenant-specific data using .eq("owner_id", userId).'
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ... Additional checks for Phase 7.4 rules ...
|
|
121
|
+
|
|
122
|
+
export function checkSensitiveLogging(content: string): SecurityViolation | null {
|
|
123
|
+
if (content.includes('console.log') && (content.includes('env') || content.includes('process.env') || content.includes('password') || content.includes('user'))) {
|
|
124
|
+
return {
|
|
125
|
+
id: 'SEC-LOG-01',
|
|
126
|
+
type: 'PII_LEAKAGE',
|
|
127
|
+
severity: 'MEDIUM',
|
|
128
|
+
title: 'Potential Log Leakage',
|
|
129
|
+
description: 'Detected console.log of potentially sensitive objects (user, env, password).',
|
|
130
|
+
recommendation: 'Use a structured logger and ensure sensitive fields are redacted before logging.'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function checkRbac(filePath: string, content: string): SecurityViolation | null {
|
|
137
|
+
if ((filePath.includes('app/api/') || filePath.includes('actions/')) && !content.includes('role') && !content.includes('permission') && !content.includes('isAdmin')) {
|
|
138
|
+
return {
|
|
139
|
+
id: 'SEC-RBAC-01',
|
|
140
|
+
type: 'AUTHORIZATION_GATHERING',
|
|
141
|
+
severity: 'MEDIUM',
|
|
142
|
+
title: 'Missing RBAC Check',
|
|
143
|
+
description: 'Route/Action seems to lack explicit role or permission-based access control.',
|
|
144
|
+
recommendation: 'Verify user roles (e.g., admin, editor) before granting access to sensitive endpoints.'
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function checkInputValidation(filePath: string, content: string): SecurityViolation | null {
|
|
151
|
+
if ((filePath.includes('app/api/') || filePath.includes('actions/')) && !content.includes('z.object') && !content.includes('parse')) {
|
|
152
|
+
if (content.includes('.update(') || content.includes('.insert(')) {
|
|
153
|
+
return {
|
|
154
|
+
id: 'SEC-VAL-01',
|
|
155
|
+
type: 'INPUT_VALIDATION',
|
|
156
|
+
severity: 'HIGH',
|
|
157
|
+
title: 'Missing Input Validation',
|
|
158
|
+
description: 'Database mutation detected without Zod schema validation.',
|
|
159
|
+
recommendation: 'Use Zod to validate all incoming data before passing it to the database.'
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function checkCleanFailures(content: string): SecurityViolation | null {
|
|
167
|
+
const errorLeakageRegex = /(?:res\..*(?:error|message|status).*\.message)|(?:throw new Error\(.*\.message\))/;
|
|
168
|
+
if (errorLeakageRegex.test(content) && content.includes('catch')) {
|
|
169
|
+
return {
|
|
170
|
+
id: 'SEC-ERR-01',
|
|
171
|
+
type: 'ERROR_EXPOSURE',
|
|
172
|
+
severity: 'MEDIUM',
|
|
173
|
+
title: 'Raw Database Error Leakage',
|
|
174
|
+
description: 'Detected raw error messages being sent/thrown to the client.',
|
|
175
|
+
recommendation: 'Catch errors and return standardized, non-technical messages to the frontend.'
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function checkDependencies(filePath: string, content: string): SecurityViolation | null {
|
|
182
|
+
if (filePath.endsWith('package.json')) {
|
|
183
|
+
if (content.includes('"*') || content.includes('"latest"')) {
|
|
184
|
+
return {
|
|
185
|
+
id: 'SEC-DEPS-01',
|
|
186
|
+
type: 'DEPENDENCY_BLOAT',
|
|
187
|
+
severity: 'HIGH',
|
|
188
|
+
title: 'Unpinned Dependencies',
|
|
189
|
+
description: 'package.json contains unpinned or "latest" versions.',
|
|
190
|
+
recommendation: 'Always pin dependencies to specific versions for reproducibility and security.'
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ... Anti-Lazy checks ...
|
|
198
|
+
|
|
199
|
+
export function checkAntiLazy(filePath: string, content: string): SecurityViolation[] {
|
|
200
|
+
const violations: SecurityViolation[] = [];
|
|
201
|
+
|
|
202
|
+
// SEC-LAZY-01: No 'any'
|
|
203
|
+
const anyRegex = /: any([,\s)\]\}]|$)/g;
|
|
204
|
+
if (anyRegex.test(content) && !filePath.includes('node_modules')) {
|
|
205
|
+
violations.push({
|
|
206
|
+
id: 'SEC-LAZY-01',
|
|
207
|
+
type: 'TYPE_LAZINESS',
|
|
208
|
+
severity: 'HIGH',
|
|
209
|
+
title: 'Use of "any" Type detected',
|
|
210
|
+
description: 'Detected use of the "any" type, which bypasses the TypeSytem safety checks.',
|
|
211
|
+
recommendation: 'Replace "any" with a specific interface, type, or "unknown" with proper narrowing.'
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// SEC-LAZY-02: No empty catch
|
|
216
|
+
const emptyCatchRegex = /catch\s*\([^)]*\)\s*{\s*}/g;
|
|
217
|
+
if (emptyCatchRegex.test(content)) {
|
|
218
|
+
violations.push({
|
|
219
|
+
id: 'SEC-LAZY-02',
|
|
220
|
+
type: 'ERROR_SWALLOWING',
|
|
221
|
+
severity: 'HIGH',
|
|
222
|
+
title: 'Empty Catch Block detected',
|
|
223
|
+
description: 'Detected a catch block that swallows errors without logging or handling them.',
|
|
224
|
+
recommendation: 'Always log the error or handle it. Never leave a catch block empty.'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// SEC-LAZY-03: No TODOs
|
|
229
|
+
if (content.includes('TODO') || content.includes('FIXME')) {
|
|
230
|
+
violations.push({
|
|
231
|
+
id: 'SEC-LAZY-03',
|
|
232
|
+
type: 'INCOMPLETE_WORK',
|
|
233
|
+
severity: 'MEDIUM',
|
|
234
|
+
title: 'Technical Debt remnant (TODO/FIXME)',
|
|
235
|
+
description: 'Detected TODO or FIXME comments in a file being evaluated for completion.',
|
|
236
|
+
recommendation: 'Resolve the task fully or move the TODO to the project roadmap before committing.'
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return violations;
|
|
241
|
+
}
|
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
2
|
import { AuditRlsStatusInput } from '../lib/types.js';
|
|
3
|
+
import * as Checks from './security-checks.js';
|
|
4
|
+
import { registry } from '../lib/tool-registry.js';
|
|
5
|
+
import { AuditRlsStatusInputSchema, AuditSecurityIntegrityInputSchema } from '../lib/schemas.js';
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// Tool Registration
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
registry.register({
|
|
12
|
+
name: 'audit_rls_status',
|
|
13
|
+
description: `Sven's Tool: Security Shield. Audits the database to ensure Row Level Security (RLS) is enforced.`,
|
|
14
|
+
schema: AuditRlsStatusInputSchema,
|
|
15
|
+
handler: async (args, context) => {
|
|
16
|
+
const result = await auditRlsStatus(context.supabase, args);
|
|
17
|
+
return { content: [{ type: 'text', text: result.summary || 'No summary available' }] };
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
registry.register({
|
|
22
|
+
name: 'audit_security_integrity',
|
|
23
|
+
description: `Frank's Tool: Security Oracle. Performs a diagnostic security audit against the Fortress Matrix.`,
|
|
24
|
+
schema: AuditSecurityIntegrityInputSchema,
|
|
25
|
+
handler: async (args, context) => {
|
|
26
|
+
const result = await auditSecurityIntegrity(context.supabase, args);
|
|
27
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
28
|
+
}
|
|
29
|
+
});
|
|
3
30
|
|
|
4
31
|
/**
|
|
5
32
|
* Sven's Tool: Security Shield
|
|
@@ -10,7 +37,6 @@ export async function auditRlsStatus(
|
|
|
10
37
|
input: AuditRlsStatusInput
|
|
11
38
|
) {
|
|
12
39
|
try {
|
|
13
|
-
// Query pg_tables to check rowsecurity status
|
|
14
40
|
const { data, error } = await supabase.rpc('execute_sql', {
|
|
15
41
|
query: `
|
|
16
42
|
SELECT tablename, rowsecurity
|
|
@@ -20,29 +46,13 @@ export async function auditRlsStatus(
|
|
|
20
46
|
`
|
|
21
47
|
});
|
|
22
48
|
|
|
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
49
|
if (error) {
|
|
36
|
-
// Try direct fetch if exposed, which is rare for system tables
|
|
37
50
|
throw new Error(`Database query failed: ${error.message}`);
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
// Parse result (assuming result comes back as array of objects)
|
|
41
|
-
// If data is null/empty but no error?
|
|
42
53
|
const tables = data as Array<{ tablename: string, rowsecurity: boolean }>;
|
|
43
54
|
|
|
44
55
|
if (!tables) {
|
|
45
|
-
// If RPC returns void or strange shape, try to warn.
|
|
46
56
|
return {
|
|
47
57
|
status: 'ERROR',
|
|
48
58
|
message: 'Could not fetch table list. Ensure execute_sql RPC is available or pg_tables is accessible.'
|
|
@@ -73,10 +83,70 @@ export async function auditRlsStatus(
|
|
|
73
83
|
};
|
|
74
84
|
|
|
75
85
|
} catch (err: any) {
|
|
76
|
-
// Fallback: If we can't reach the DB
|
|
77
86
|
return {
|
|
78
87
|
status: 'ERROR',
|
|
79
88
|
message: `Sven could not reach the database structure. Error: ${err.message || err}. check connection strings.`
|
|
80
89
|
};
|
|
81
90
|
}
|
|
82
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Frank's Tool: Security Oracle
|
|
95
|
+
* Performs a diagnostic security audit against the Fortress Matrix.
|
|
96
|
+
*/
|
|
97
|
+
export async function auditSecurityIntegrity(
|
|
98
|
+
supabase: SupabaseClient,
|
|
99
|
+
input: { projectId: string, filePath: string, content: string }
|
|
100
|
+
) {
|
|
101
|
+
const violations: any[] = [];
|
|
102
|
+
const { content, filePath } = input;
|
|
103
|
+
|
|
104
|
+
// Use Modular Checks
|
|
105
|
+
const sqlViolation = Checks.checkSqlInjection(content);
|
|
106
|
+
if (sqlViolation) violations.push(sqlViolation);
|
|
107
|
+
|
|
108
|
+
const rlsViolation = Checks.checkRlsInMigration(filePath, content);
|
|
109
|
+
if (rlsViolation) violations.push(rlsViolation);
|
|
110
|
+
|
|
111
|
+
const keyViolations = Checks.checkHardcodedSecrets(filePath, content);
|
|
112
|
+
violations.push(...keyViolations);
|
|
113
|
+
|
|
114
|
+
const xssViolation = Checks.checkXss(content);
|
|
115
|
+
if (xssViolation) violations.push(xssViolation);
|
|
116
|
+
|
|
117
|
+
const authViolation = Checks.checkIdentityIsolation(filePath, content);
|
|
118
|
+
if (authViolation) violations.push(authViolation);
|
|
119
|
+
|
|
120
|
+
const logViolation = Checks.checkSensitiveLogging(content);
|
|
121
|
+
if (logViolation) violations.push(logViolation);
|
|
122
|
+
|
|
123
|
+
const rbacViolation = Checks.checkRbac(filePath, content);
|
|
124
|
+
if (rbacViolation) violations.push(rbacViolation);
|
|
125
|
+
|
|
126
|
+
const valViolation = Checks.checkInputValidation(filePath, content);
|
|
127
|
+
if (valViolation) violations.push(valViolation);
|
|
128
|
+
|
|
129
|
+
const errViolation = Checks.checkCleanFailures(content);
|
|
130
|
+
if (errViolation) violations.push(errViolation);
|
|
131
|
+
|
|
132
|
+
const depViolation = Checks.checkDependencies(filePath, content);
|
|
133
|
+
if (depViolation) violations.push(depViolation);
|
|
134
|
+
|
|
135
|
+
const lazyViolations = Checks.checkAntiLazy(filePath, content);
|
|
136
|
+
violations.push(...lazyViolations);
|
|
137
|
+
|
|
138
|
+
const score = Math.max(0, 100 - (violations.length * 10));
|
|
139
|
+
const passed = !violations.some((v: any) => v.severity === 'HIGH' || v.severity === 'FATAL');
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
timestamp: new Date().toISOString(),
|
|
143
|
+
projectId: input.projectId,
|
|
144
|
+
filePath: input.filePath,
|
|
145
|
+
passed,
|
|
146
|
+
score,
|
|
147
|
+
violations,
|
|
148
|
+
summary: passed
|
|
149
|
+
? 'FRANK: Security audit PASSED. No critical Fortress violations detected.'
|
|
150
|
+
: `FRANK: Security audit FAILED. Detected ${violations.length} violations against the Fortress Matrix.`
|
|
151
|
+
};
|
|
152
|
+
}
|
package/src/tools/submit-idea.ts
CHANGED
|
@@ -7,6 +7,31 @@
|
|
|
7
7
|
|
|
8
8
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
9
9
|
import type { SubmitIdeaResponse } from '../lib/types.js';
|
|
10
|
+
import { registry } from '../lib/tool-registry.js';
|
|
11
|
+
import { SubmitIdeaInputSchema } from '../lib/schemas.js';
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// Tool Registration
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
registry.register({
|
|
18
|
+
name: 'submit_idea',
|
|
19
|
+
description: `Submits a new idea to the Idea Lab (saved_ideas table).
|
|
20
|
+
Ideas can later be reviewed, analyzed, and promoted to features.`,
|
|
21
|
+
schema: SubmitIdeaInputSchema,
|
|
22
|
+
handler: async (args, context) => {
|
|
23
|
+
const result = await submitIdea(
|
|
24
|
+
context.supabase,
|
|
25
|
+
context.userId,
|
|
26
|
+
args.projectId,
|
|
27
|
+
args.title,
|
|
28
|
+
args.description,
|
|
29
|
+
args.category,
|
|
30
|
+
args.tags
|
|
31
|
+
);
|
|
32
|
+
return { content: [{ type: 'text', text: result.message }] };
|
|
33
|
+
}
|
|
34
|
+
});
|
|
10
35
|
|
|
11
36
|
export async function submitIdea(
|
|
12
37
|
supabase: SupabaseClient,
|
|
@@ -9,6 +9,34 @@ import {
|
|
|
9
9
|
IDEProvider,
|
|
10
10
|
RuleFile
|
|
11
11
|
} from '@rigstate/rules-engine';
|
|
12
|
+
import { registry } from '../lib/tool-registry.js';
|
|
13
|
+
import { GenerateCursorRulesInputSchema } from '../lib/schemas.js';
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// Tool Registration
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
registry.register({
|
|
20
|
+
name: 'sync_ide_rules',
|
|
21
|
+
description: `Generates the appropriate rules file content (e.g. .cursorrules, .windsurfrules)
|
|
22
|
+
based on project context and user settings.`,
|
|
23
|
+
schema: GenerateCursorRulesInputSchema,
|
|
24
|
+
handler: async (args, context) => {
|
|
25
|
+
const result = await syncIdeRules(context.supabase, args.projectId);
|
|
26
|
+
|
|
27
|
+
// Format response: Main file content + information about modular files
|
|
28
|
+
let responseText = `FileName: ${result.fileName}\n\nContent:\n${result.content}`;
|
|
29
|
+
|
|
30
|
+
if (result.files && result.files.length > 0) {
|
|
31
|
+
responseText += `\n\n--- MODULAR FILES GENERATED (${result.files.length}) ---\n`;
|
|
32
|
+
responseText += result.files.map(f => `- ${f.path}`).join('\n');
|
|
33
|
+
responseText += `\n\nNote: Please ensure these modular files are also updated if your IDE is following the V3 Rules standard.`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { content: [{ type: 'text', text: responseText }] };
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
12
40
|
|
|
13
41
|
/**
|
|
14
42
|
* Sync IDE rules for a project.
|
|
@@ -37,16 +65,19 @@ export async function syncIdeRules(
|
|
|
37
65
|
const ide: IDEProvider = (project.preferred_ide as IDEProvider) || 'cursor';
|
|
38
66
|
|
|
39
67
|
// 3. Fetch Context Data (Parallel for speed)
|
|
40
|
-
const [stack, roadmapRes, legacyStats, activeAgents] = await Promise.all([
|
|
68
|
+
const [stack, roadmapRes, legacyStats, activeAgents, dbMetadataRes] = await Promise.all([
|
|
41
69
|
fetchProjectTechStack(supabase, projectId),
|
|
42
70
|
supabase
|
|
43
71
|
.from('roadmap_chunks')
|
|
44
72
|
.select('step_number, title, status, sprint_focus, prompt_content, is_legacy')
|
|
45
73
|
.eq('project_id', projectId),
|
|
46
74
|
fetchLegacyStats(supabase, projectId),
|
|
47
|
-
fetchActiveAgents(supabase)
|
|
75
|
+
fetchActiveAgents(supabase),
|
|
76
|
+
supabase.rpc('get_table_metadata')
|
|
48
77
|
]);
|
|
49
78
|
|
|
79
|
+
const databaseMetadata = dbMetadataRes.data || [];
|
|
80
|
+
|
|
50
81
|
// 4. Generate Content (Mono-file)
|
|
51
82
|
const content = generateRuleContent(
|
|
52
83
|
{ ...project, id: projectId },
|
|
@@ -64,7 +95,8 @@ export async function syncIdeRules(
|
|
|
64
95
|
roadmapRes.data || [],
|
|
65
96
|
ide,
|
|
66
97
|
legacyStats,
|
|
67
|
-
activeAgents
|
|
98
|
+
activeAgents,
|
|
99
|
+
databaseMetadata
|
|
68
100
|
);
|
|
69
101
|
|
|
70
102
|
return {
|
|
@@ -4,8 +4,30 @@
|
|
|
4
4
|
* Allows Frank to send logic corrections and fetch learned behaviors.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { z } from 'zod';
|
|
7
8
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
8
9
|
import { v4 as uuidv4 } from 'uuid';
|
|
10
|
+
import { registry } from '../lib/tool-registry.js';
|
|
11
|
+
import { submitSignal } from '../lib/curator/index.js';
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// Schemas
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
const RefineLogicSchema = z.object({
|
|
18
|
+
projectId: z.string().describe('The UUID of the Rigstate project'),
|
|
19
|
+
originalReasoning: z.string().describe('What Frank originally said or did wrong'),
|
|
20
|
+
userCorrection: z.string().describe('How Frank should handle this in the future'),
|
|
21
|
+
scope: z.enum(['project', 'global']).default('project').describe('Whether this correction applies to this project only or all projects (default: project)')
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const GetLearnedInstructionsSchema = z.object({
|
|
25
|
+
projectId: z.string().optional().describe('Optional project ID to include project-specific instructions')
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// ============================================
|
|
29
|
+
// Types
|
|
30
|
+
// ============================================
|
|
9
31
|
|
|
10
32
|
export interface RefineLogicResponse {
|
|
11
33
|
success: boolean;
|
|
@@ -25,17 +47,20 @@ export interface LearnedInstructionsResponse {
|
|
|
25
47
|
formatted: string;
|
|
26
48
|
}
|
|
27
49
|
|
|
50
|
+
// ============================================
|
|
51
|
+
// Implementation
|
|
52
|
+
// ============================================
|
|
53
|
+
|
|
28
54
|
/**
|
|
29
55
|
* Send a logic correction to the Rigstate cloud database
|
|
30
56
|
*/
|
|
31
57
|
export async function refineLogic(
|
|
32
58
|
supabase: SupabaseClient,
|
|
33
59
|
userId: string,
|
|
34
|
-
|
|
35
|
-
originalReasoning: string,
|
|
36
|
-
userCorrection: string,
|
|
37
|
-
scope: 'project' | 'global'
|
|
60
|
+
args: z.infer<typeof RefineLogicSchema>
|
|
38
61
|
): Promise<RefineLogicResponse> {
|
|
62
|
+
const { projectId, originalReasoning, userCorrection, scope } = args;
|
|
63
|
+
|
|
39
64
|
// Generate a trace ID for this action
|
|
40
65
|
const traceId = uuidv4();
|
|
41
66
|
|
|
@@ -44,14 +69,14 @@ export async function refineLogic(
|
|
|
44
69
|
.from('projects')
|
|
45
70
|
.select('id, name')
|
|
46
71
|
.eq('id', projectId)
|
|
47
|
-
.eq('
|
|
72
|
+
.eq('owner_id', userId)
|
|
48
73
|
.single();
|
|
49
74
|
|
|
50
75
|
if (projectError || !project) {
|
|
51
76
|
throw new Error(`Project access denied or not found: ${projectId}`);
|
|
52
77
|
}
|
|
53
78
|
|
|
54
|
-
// Save the instruction
|
|
79
|
+
// Save the instruction to Raw Memory (ai_instructions)
|
|
55
80
|
const { data: instruction, error: insertError } = await supabase
|
|
56
81
|
.from('ai_instructions')
|
|
57
82
|
.insert({
|
|
@@ -61,7 +86,7 @@ export async function refineLogic(
|
|
|
61
86
|
context: originalReasoning,
|
|
62
87
|
is_global: scope === 'global',
|
|
63
88
|
priority: scope === 'global' ? 8 : 6,
|
|
64
|
-
source_log_id: null
|
|
89
|
+
source_log_id: null
|
|
65
90
|
})
|
|
66
91
|
.select('id')
|
|
67
92
|
.single();
|
|
@@ -70,6 +95,30 @@ export async function refineLogic(
|
|
|
70
95
|
throw new Error(`Failed to save instruction: ${insertError.message}`);
|
|
71
96
|
}
|
|
72
97
|
|
|
98
|
+
let message = `ā
Correction saved! Frank will apply this ${scope === 'global' ? 'globally' : 'to this project'}.`;
|
|
99
|
+
|
|
100
|
+
// Phase 8.5.5 Integration: Global corrections trigger Curator Signal
|
|
101
|
+
if (scope === 'global') {
|
|
102
|
+
try {
|
|
103
|
+
await submitSignal(supabase, userId, {
|
|
104
|
+
projectId,
|
|
105
|
+
title: `Teacher Correction: ${userCorrection.substring(0, 30)}...`,
|
|
106
|
+
instruction: userCorrection,
|
|
107
|
+
category: 'MAINTAINABILITY', // Default
|
|
108
|
+
severity: 'MEDIUM',
|
|
109
|
+
reasoning: `Teacher Mode correction (Trace: ${traceId}). Original reasoning: ${originalReasoning.substring(0, 100)}...`,
|
|
110
|
+
source_type: 'TEACHER_MODE' // Using a tag via reasoning or title since source_type in schema is rigid?
|
|
111
|
+
// actually check schema: framework_tags? No source_type is not in schema input, it's fixed in logic.
|
|
112
|
+
// Wait, submitSignal implementation in curator/index.ts sets source_type: 'mcp_submission'.
|
|
113
|
+
// I will update the reasoning to indicate Teacher Mode.
|
|
114
|
+
});
|
|
115
|
+
message += `\n\nš”ļø CURATOR PROTOCOL: Signal submitted to Sigrid for global verification.`;
|
|
116
|
+
} catch (e: any) {
|
|
117
|
+
console.error('Failed to auto-submit to curator:', e);
|
|
118
|
+
message += `\n(Note: Failed to submit to curator: ${e.message})`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
73
122
|
// Log this MCP action with trace
|
|
74
123
|
await supabase
|
|
75
124
|
.from('ai_activity_log')
|
|
@@ -91,7 +140,7 @@ export async function refineLogic(
|
|
|
91
140
|
success: true,
|
|
92
141
|
instructionId: instruction.id,
|
|
93
142
|
traceId,
|
|
94
|
-
message
|
|
143
|
+
message
|
|
95
144
|
};
|
|
96
145
|
}
|
|
97
146
|
|
|
@@ -101,8 +150,10 @@ export async function refineLogic(
|
|
|
101
150
|
export async function getLearnedInstructions(
|
|
102
151
|
supabase: SupabaseClient,
|
|
103
152
|
userId: string,
|
|
104
|
-
|
|
153
|
+
args: z.infer<typeof GetLearnedInstructionsSchema>
|
|
105
154
|
): Promise<LearnedInstructionsResponse> {
|
|
155
|
+
const { projectId } = args;
|
|
156
|
+
|
|
106
157
|
// Fetch user-specific instructions
|
|
107
158
|
let query = supabase
|
|
108
159
|
.from('ai_instructions')
|
|
@@ -133,10 +184,10 @@ export async function getLearnedInstructions(
|
|
|
133
184
|
.order('priority', { ascending: false })
|
|
134
185
|
.limit(10);
|
|
135
186
|
|
|
136
|
-
const globalInstructions = (globalBase || []).map(g => g.instruction);
|
|
187
|
+
const globalInstructions = (globalBase || []).map((g: any) => g.instruction);
|
|
137
188
|
|
|
138
189
|
// Format for context injection
|
|
139
|
-
const instructions = (userInstructions || []).map(i => ({
|
|
190
|
+
const instructions = (userInstructions || []).map((i: any) => ({
|
|
140
191
|
instruction: i.instruction as string,
|
|
141
192
|
priority: i.priority as number,
|
|
142
193
|
isGlobal: i.is_global as boolean,
|
|
@@ -147,13 +198,13 @@ export async function getLearnedInstructions(
|
|
|
147
198
|
|
|
148
199
|
if (globalInstructions.length > 0) {
|
|
149
200
|
formatted += `## GLOBAL RIGSTATE STANDARDS\n`;
|
|
150
|
-
formatted += globalInstructions.map(g => `- ${g}`).join('\n');
|
|
201
|
+
formatted += globalInstructions.map((g: string) => `- ${g}`).join('\n');
|
|
151
202
|
formatted += '\n\n';
|
|
152
203
|
}
|
|
153
204
|
|
|
154
205
|
if (instructions.length > 0) {
|
|
155
206
|
formatted += `## USER CORRECTIONS & TUNING\n`;
|
|
156
|
-
formatted += instructions.map(i => {
|
|
207
|
+
formatted += instructions.map((i: any) => {
|
|
157
208
|
const scope = i.isGlobal ? '[GLOBAL]' : '[PROJECT]';
|
|
158
209
|
return `- ${scope} ${i.instruction}`;
|
|
159
210
|
}).join('\n');
|
|
@@ -169,3 +220,31 @@ export async function getLearnedInstructions(
|
|
|
169
220
|
formatted
|
|
170
221
|
};
|
|
171
222
|
}
|
|
223
|
+
|
|
224
|
+
// ============================================
|
|
225
|
+
// Tool Registration
|
|
226
|
+
// ============================================
|
|
227
|
+
|
|
228
|
+
registry.register({
|
|
229
|
+
name: 'refine_logic',
|
|
230
|
+
description: `Send a logic correction to teach Frank how to handle similar situations.
|
|
231
|
+
Use this when Frank made a reasoning error and you want to correct it.
|
|
232
|
+
The correction will be saved and applied to future interactions.`,
|
|
233
|
+
schema: RefineLogicSchema,
|
|
234
|
+
handler: async (args, context) => {
|
|
235
|
+
const result = await refineLogic(context.supabase, context.userId, args);
|
|
236
|
+
return { content: [{ type: 'text', text: result.message }] };
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
registry.register({
|
|
241
|
+
name: 'get_learned_instructions',
|
|
242
|
+
description: `Fetch all learned behaviors and corrections from the database.
|
|
243
|
+
Use this to inject prior corrections into your context window.
|
|
244
|
+
Returns both user-specific and global Rigstate standards.`,
|
|
245
|
+
schema: GetLearnedInstructionsSchema,
|
|
246
|
+
handler: async (args, context) => {
|
|
247
|
+
const result = await getLearnedInstructions(context.supabase, context.userId, args);
|
|
248
|
+
return { content: [{ type: 'text', text: result.formatted }] };
|
|
249
|
+
}
|
|
250
|
+
});
|