@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.
Files changed (57) hide show
  1. package/.agent/skills/client-side-notification-logger/SKILL.md +139 -0
  2. package/.agent/skills/react-state-counter/SKILL.md +73 -0
  3. package/.agent/skills/rigstate-evolutionary-refactor/SKILL.md +40 -0
  4. package/.agent/skills/rigstate-integrity-gate/SKILL.md +55 -0
  5. package/.agent/skills/rigstate-legacy-renovator/SKILL.md +12 -0
  6. package/.agent/skills/sec-auth-04/SKILL.md +22 -0
  7. package/.agent/skills/sec-key-01/SKILL.md +21 -0
  8. package/.agent/skills/sec-rls-01/SKILL.md +22 -0
  9. package/.agent/skills/sec-sql-01/SKILL.md +23 -0
  10. package/.agent/skills/sec-ui-01/SKILL.md +21 -0
  11. package/.cursor/rules/rigstate-database.mdc +89 -0
  12. package/.cursor/rules/rigstate-guardian.mdc +43 -0
  13. package/.cursor/rules/rigstate-identity.mdc +45 -0
  14. package/.cursor/rules/rigstate-roadmap.mdc +9 -0
  15. package/.cursor/rules/rigstate-workflow.mdc +323 -0
  16. package/.cursorrules +402 -0
  17. package/AGENTS.md +34 -0
  18. package/dist/index.js +2604 -3067
  19. package/dist/index.js.map +1 -1
  20. package/package.json +2 -2
  21. package/roadmap.json +815 -21
  22. package/src/index.ts +16 -1765
  23. package/src/lib/context-engine.ts +85 -0
  24. package/src/lib/curator/actions/fortress.ts +77 -0
  25. package/src/lib/curator/actions/query.ts +73 -0
  26. package/src/lib/curator/actions/stats.ts +70 -0
  27. package/src/lib/curator/actions/submit.ts +190 -0
  28. package/src/lib/curator/index.ts +10 -0
  29. package/src/lib/curator/schemas.ts +37 -0
  30. package/src/lib/schemas.ts +191 -0
  31. package/src/lib/types.ts +102 -261
  32. package/src/server/core.ts +40 -0
  33. package/src/server/factory.ts +78 -0
  34. package/src/server/telemetry.ts +122 -0
  35. package/src/server/types.ts +21 -0
  36. package/src/tools/analyze-database-performance.ts +157 -0
  37. package/src/tools/arch-tools.ts +16 -0
  38. package/src/tools/audit-integrity-gate.ts +166 -0
  39. package/src/tools/check-rules-sync.ts +20 -0
  40. package/src/tools/complete-roadmap-task.ts +88 -31
  41. package/src/tools/curator-tools.ts +74 -0
  42. package/src/tools/get-latest-decisions.ts +22 -0
  43. package/src/tools/get-next-roadmap-step.ts +21 -0
  44. package/src/tools/get-project-context.ts +35 -1
  45. package/src/tools/index.ts +7 -0
  46. package/src/tools/list-features.ts +4 -1
  47. package/src/tools/list-roadmap-tasks.ts +21 -0
  48. package/src/tools/planning-tools.ts +40 -0
  49. package/src/tools/query-brain.ts +25 -1
  50. package/src/tools/run-architecture-audit.ts +23 -0
  51. package/src/tools/save-decision.ts +26 -0
  52. package/src/tools/security-checks.ts +241 -0
  53. package/src/tools/security-tools.ts +88 -18
  54. package/src/tools/submit-idea.ts +25 -0
  55. package/src/tools/sync-ide-rules.ts +35 -3
  56. package/src/tools/teacher-mode.ts +92 -13
  57. 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
+ }
@@ -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
- projectId: string,
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('user_id', userId)
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 // MCP actions don't have a log ID
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: `āœ… Correction saved! Frank will apply this ${scope === 'global' ? 'globally' : 'to this project'}.`
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
- projectId?: string
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
+ });