@rigstate/mcp 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/README.md +352 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3445 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
- package/roadmap.json +531 -0
- package/src/agents/the-scribe.ts +122 -0
- package/src/index.ts +1792 -0
- package/src/lib/supabase.ts +120 -0
- package/src/lib/tool-registry.ts +134 -0
- package/src/lib/types.ts +415 -0
- package/src/lib/utils.ts +10 -0
- package/src/resources/project-morals.ts +92 -0
- package/src/tools/arch-tools.ts +166 -0
- package/src/tools/archaeological-scan.ts +335 -0
- package/src/tools/check-agent-bridge.ts +169 -0
- package/src/tools/check-rules-sync.ts +85 -0
- package/src/tools/complete-roadmap-task.ts +96 -0
- package/src/tools/generate-professional-pdf.ts +232 -0
- package/src/tools/get-latest-decisions.ts +130 -0
- package/src/tools/get-next-roadmap-step.ts +76 -0
- package/src/tools/get-project-context.ts +163 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/list-features.ts +67 -0
- package/src/tools/list-roadmap-tasks.ts +61 -0
- package/src/tools/pending-tasks.ts +228 -0
- package/src/tools/planning-tools.ts +123 -0
- package/src/tools/query-brain.ts +125 -0
- package/src/tools/research-tools.ts +149 -0
- package/src/tools/run-architecture-audit.ts +203 -0
- package/src/tools/save-decision.ts +77 -0
- package/src/tools/security-tools.ts +82 -0
- package/src/tools/submit-idea.ts +66 -0
- package/src/tools/sync-ide-rules.ts +76 -0
- package/src/tools/teacher-mode.ts +171 -0
- package/src/tools/ui-tools.ts +191 -0
- package/src/tools/update-roadmap.ts +105 -0
- package/tsconfig.json +29 -0
- package/tsup.config.ts +16 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3445 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/index.ts
|
|
10
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import fs4 from "fs/promises";
|
|
13
|
+
import path4 from "path";
|
|
14
|
+
import os from "os";
|
|
15
|
+
import {
|
|
16
|
+
CallToolRequestSchema,
|
|
17
|
+
ListToolsRequestSchema,
|
|
18
|
+
ListResourcesRequestSchema,
|
|
19
|
+
ReadResourceRequestSchema,
|
|
20
|
+
ErrorCode,
|
|
21
|
+
McpError
|
|
22
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
23
|
+
|
|
24
|
+
// src/lib/supabase.ts
|
|
25
|
+
import { createClient as createSupabaseClient } from "@supabase/supabase-js";
|
|
26
|
+
import { createHash } from "crypto";
|
|
27
|
+
var PRODUCTION_SUPABASE_URL = "https://gseblsxnfppsxbmtzcfj.supabase.co";
|
|
28
|
+
var PRODUCTION_SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdzZWJsc3huZnBwc3hibXR6Y2ZqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzYxODIxNjgsImV4cCI6MjA1MTc1ODE2OH0.bltFf6_gNH-p3jH8lRY8dCRK6fmgxO8Hjp7UGx8xSxY";
|
|
29
|
+
var PRODUCTION_SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY || "";
|
|
30
|
+
var SUPABASE_URL = process.env.RIGSTATE_SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL || PRODUCTION_SUPABASE_URL;
|
|
31
|
+
var SUPABASE_ANON_KEY = process.env.RIGSTATE_SUPABASE_ANON_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || PRODUCTION_SUPABASE_ANON_KEY;
|
|
32
|
+
var SUPABASE_SERVICE_KEY = process.env.RIGSTATE_SUPABASE_SERVICE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY || PRODUCTION_SUPABASE_SERVICE_KEY;
|
|
33
|
+
async function authenticateApiKey(apiKey) {
|
|
34
|
+
if (!apiKey) {
|
|
35
|
+
return { success: false, error: "RIGSTATE_API_KEY is required" };
|
|
36
|
+
}
|
|
37
|
+
if (!apiKey.startsWith("sk_rigstate_")) {
|
|
38
|
+
return { success: false, error: "Invalid API key format. Expected sk_rigstate_..." };
|
|
39
|
+
}
|
|
40
|
+
if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
error: "Supabase configuration missing. Set RIGSTATE_SUPABASE_URL and RIGSTATE_SUPABASE_ANON_KEY."
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const hashedKey = createHash("sha256").update(apiKey).digest("hex");
|
|
47
|
+
const clientKey = SUPABASE_SERVICE_KEY || SUPABASE_ANON_KEY;
|
|
48
|
+
const supabase = createSupabaseClient(SUPABASE_URL, clientKey);
|
|
49
|
+
const { data: keyData, error: keyError } = await supabase.from("api_keys").select("id, user_id, project_id, organization_id, scope").eq("key_hash", hashedKey).single();
|
|
50
|
+
if (keyError || !keyData) {
|
|
51
|
+
return { success: false, error: "Invalid or revoked API key" };
|
|
52
|
+
}
|
|
53
|
+
supabase.from("api_keys").update({ last_used_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", keyData.id).then();
|
|
54
|
+
const userSupabase = createSupabaseClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
context: {
|
|
58
|
+
userId: keyData.user_id,
|
|
59
|
+
apiKeyId: keyData.id,
|
|
60
|
+
supabase: userSupabase
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/lib/types.ts
|
|
66
|
+
import { z } from "zod";
|
|
67
|
+
var QueryBrainInputSchema = z.object({
|
|
68
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
69
|
+
query: z.string().min(1, "Query is required"),
|
|
70
|
+
limit: z.number().min(1).max(20).optional().default(8),
|
|
71
|
+
threshold: z.number().min(0).max(1).optional().default(0.5)
|
|
72
|
+
});
|
|
73
|
+
var GetProjectContextInputSchema = z.object({
|
|
74
|
+
projectId: z.string().uuid("Invalid project ID")
|
|
75
|
+
});
|
|
76
|
+
var GetLatestDecisionsInputSchema = z.object({
|
|
77
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
78
|
+
limit: z.number().min(1).max(10).optional().default(5)
|
|
79
|
+
});
|
|
80
|
+
var SaveDecisionInputSchema = z.object({
|
|
81
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
82
|
+
title: z.string().min(1, "Title is required").max(200, "Title too long"),
|
|
83
|
+
decision: z.string().min(1, "Decision content is required"),
|
|
84
|
+
rationale: z.string().optional(),
|
|
85
|
+
category: z.enum(["decision", "architecture", "constraint", "tech_stack", "design_rule"]).optional().default("decision"),
|
|
86
|
+
tags: z.array(z.string()).optional().default([])
|
|
87
|
+
});
|
|
88
|
+
var SubmitIdeaInputSchema = z.object({
|
|
89
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
90
|
+
title: z.string().min(1, "Title is required").max(200, "Title too long"),
|
|
91
|
+
description: z.string().min(1, "Description is required"),
|
|
92
|
+
category: z.enum(["feature", "improvement", "experiment", "pivot"]).optional().default("feature"),
|
|
93
|
+
tags: z.array(z.string()).optional().default([])
|
|
94
|
+
});
|
|
95
|
+
var UpdateRoadmapInputSchema = z.object({
|
|
96
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
97
|
+
chunkId: z.string().uuid("Invalid chunk ID").optional(),
|
|
98
|
+
title: z.string().optional(),
|
|
99
|
+
status: z.enum(["LOCKED", "ACTIVE", "COMPLETED"])
|
|
100
|
+
});
|
|
101
|
+
var RunArchitectureAuditInputSchema = z.object({
|
|
102
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
103
|
+
filePath: z.string().min(1, "File path is required"),
|
|
104
|
+
content: z.string().min(1, "Content is required")
|
|
105
|
+
});
|
|
106
|
+
var ListRoadmapTasksInputSchema = z.object({
|
|
107
|
+
projectId: z.string().uuid("Invalid project ID")
|
|
108
|
+
});
|
|
109
|
+
var RefineLogicInputSchema = z.object({
|
|
110
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
111
|
+
originalReasoning: z.string().min(1, "Original reasoning is required"),
|
|
112
|
+
userCorrection: z.string().min(1, "User correction is required"),
|
|
113
|
+
scope: z.enum(["project", "global"]).optional().default("project")
|
|
114
|
+
});
|
|
115
|
+
var GetLearnedInstructionsInputSchema = z.object({
|
|
116
|
+
projectId: z.string().uuid("Invalid project ID").optional()
|
|
117
|
+
});
|
|
118
|
+
var CheckAgentBridgeInputSchema = z.object({
|
|
119
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
120
|
+
action: z.enum(["check", "update", "submit_for_review"]).optional().default("check"),
|
|
121
|
+
bridgeId: z.string().uuid("Invalid bridge ID").optional(),
|
|
122
|
+
status: z.enum(["PENDING", "NEEDS_REVIEW", "AWAITING_APPROVAL", "APPROVED", "REJECTED", "EXECUTING", "COMPLETED", "FAILED"]).optional(),
|
|
123
|
+
summary: z.string().optional(),
|
|
124
|
+
execution_summary: z.string().optional(),
|
|
125
|
+
proposal: z.string().optional()
|
|
126
|
+
});
|
|
127
|
+
var CheckRulesSyncInputSchema = z.object({
|
|
128
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
129
|
+
currentRulesContent: z.string().optional()
|
|
130
|
+
// Provide content to check, or tool tries to read file
|
|
131
|
+
});
|
|
132
|
+
var GetPendingTasksInputSchema = z.object({
|
|
133
|
+
projectId: z.string().uuid("Invalid project ID")
|
|
134
|
+
});
|
|
135
|
+
var UpdateTaskStatusInputSchema = z.object({
|
|
136
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
137
|
+
taskId: z.string().uuid("Invalid task ID"),
|
|
138
|
+
status: z.enum(["EXECUTING", "COMPLETED", "FAILED"]),
|
|
139
|
+
executionSummary: z.string().optional()
|
|
140
|
+
});
|
|
141
|
+
var GetNextRoadmapStepInputSchema = z.object({
|
|
142
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
143
|
+
currentStepId: z.string().uuid("Invalid step ID").optional()
|
|
144
|
+
});
|
|
145
|
+
var GenerateProfessionalPDFInputSchema = z.object({
|
|
146
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
147
|
+
reportType: z.enum(["SYSTEM_MANIFEST", "INVESTOR_REPORT"])
|
|
148
|
+
});
|
|
149
|
+
var ArchaeologicalScanInputSchema = z.object({
|
|
150
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
151
|
+
gitLog: z.string().describe("Git log output in format: hash:X\\ndate:X\\nauthor:X\\nmessage\\n---COMMIT---\\n..."),
|
|
152
|
+
fileTree: z.array(z.string()).describe("Array of file/directory paths in the repository")
|
|
153
|
+
});
|
|
154
|
+
var ImportGhostFeaturesInputSchema = z.object({
|
|
155
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
156
|
+
features: z.array(z.object({
|
|
157
|
+
id: z.string(),
|
|
158
|
+
title: z.string(),
|
|
159
|
+
description: z.string(),
|
|
160
|
+
status: z.literal("COMPLETED"),
|
|
161
|
+
source: z.enum(["git", "filesystem", "combined"]),
|
|
162
|
+
evidence: z.array(z.string()),
|
|
163
|
+
estimatedCompletionDate: z.string(),
|
|
164
|
+
priority: z.number()
|
|
165
|
+
}))
|
|
166
|
+
});
|
|
167
|
+
var AnalyzeDependencyGraphInputSchema = z.object({
|
|
168
|
+
path: z.string().min(1, "Path is required").default("src")
|
|
169
|
+
});
|
|
170
|
+
var AuditRlsStatusInputSchema = z.object({
|
|
171
|
+
projectId: z.string().uuid("Invalid project ID").optional()
|
|
172
|
+
});
|
|
173
|
+
var QueryProjectBrainInputSchema = QueryBrainInputSchema;
|
|
174
|
+
var FetchPackageHealthInputSchema = z.object({
|
|
175
|
+
packageName: z.string().min(1, "Package name is required")
|
|
176
|
+
});
|
|
177
|
+
var SaveToProjectBrainInputSchema = z.object({
|
|
178
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
179
|
+
title: z.string().min(1, "Title is required"),
|
|
180
|
+
content: z.string().min(1, "Content is required"),
|
|
181
|
+
category: z.enum(["DECISION", "ARCHITECTURE", "NOTE", "LESSON_LEARNED"]).default("NOTE"),
|
|
182
|
+
tags: z.array(z.string()).optional().default([])
|
|
183
|
+
});
|
|
184
|
+
var UpdateRoadmapStatusInputSchema = z.object({
|
|
185
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
186
|
+
chunkId: z.string().uuid("Invalid chunk ID"),
|
|
187
|
+
status: z.enum(["TODO", "IN_PROGRESS", "COMPLETED"])
|
|
188
|
+
});
|
|
189
|
+
var AddRoadmapChunkInputSchema = z.object({
|
|
190
|
+
projectId: z.string().uuid("Invalid project ID"),
|
|
191
|
+
title: z.string().min(1, "Title is required"),
|
|
192
|
+
description: z.string().optional(),
|
|
193
|
+
featureId: z.string().optional(),
|
|
194
|
+
priority: z.enum(["LOW", "MEDIUM", "HIGH"]).default("MEDIUM")
|
|
195
|
+
});
|
|
196
|
+
var AnalyzeUiComponentInputSchema = z.object({
|
|
197
|
+
filePath: z.string().describe("Absolute path to the component file (.tsx, .css)")
|
|
198
|
+
});
|
|
199
|
+
var ApplyDesignSystemInputSchema = z.object({
|
|
200
|
+
filePath: z.string().describe("Absolute path to the file to fix")
|
|
201
|
+
});
|
|
202
|
+
var FetchUiLibraryDocsInputSchema = z.object({
|
|
203
|
+
componentName: z.string().describe('Name of the component (e.g. "button", "card")'),
|
|
204
|
+
library: z.enum(["shadcn", "lucide"]).default("shadcn")
|
|
205
|
+
});
|
|
206
|
+
var GenerateCursorRulesInputSchema = z.object({
|
|
207
|
+
projectId: z.string().uuid("Invalid project ID")
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// src/tools/get-project-context.ts
|
|
211
|
+
var KEY_LIBS = {
|
|
212
|
+
"next": "Next.js",
|
|
213
|
+
"react": "React",
|
|
214
|
+
"vue": "Vue",
|
|
215
|
+
"svelte": "Svelte",
|
|
216
|
+
"angular": "Angular",
|
|
217
|
+
"tailwindcss": "Tailwind",
|
|
218
|
+
"prisma": "Prisma",
|
|
219
|
+
"drizzle-orm": "Drizzle",
|
|
220
|
+
"@supabase/supabase-js": "Supabase",
|
|
221
|
+
"stripe": "Stripe",
|
|
222
|
+
"openai": "OpenAI",
|
|
223
|
+
"typescript": "TypeScript",
|
|
224
|
+
"framer-motion": "Framer Motion",
|
|
225
|
+
"zod": "Zod",
|
|
226
|
+
"ai": "Vercel AI"
|
|
227
|
+
};
|
|
228
|
+
async function getProjectContext(supabase, userId, projectId) {
|
|
229
|
+
const { data: project, error: projectError } = await supabase.from("projects").select("id, name, description, project_type, created_at, last_indexed_at, detected_stack, repository_tree").eq("id", projectId).eq("owner_id", userId).single();
|
|
230
|
+
if (projectError || !project) {
|
|
231
|
+
throw new Error("Project not found or access denied");
|
|
232
|
+
}
|
|
233
|
+
const { data: agentTasks } = await supabase.from("agent_bridge").select("id, roadmap_chunks(title), execution_summary, completed_at").eq("project_id", projectId).eq("status", "COMPLETED").order("completed_at", { ascending: false }).limit(3);
|
|
234
|
+
const { data: roadmapItems } = await supabase.from("roadmap_chunks").select("title, status, updated_at").eq("project_id", projectId).order("updated_at", { ascending: false }).limit(3);
|
|
235
|
+
const techStack = {
|
|
236
|
+
framework: null,
|
|
237
|
+
orm: null,
|
|
238
|
+
database: null,
|
|
239
|
+
keyLibraries: [],
|
|
240
|
+
topFolders: []
|
|
241
|
+
};
|
|
242
|
+
if (project.detected_stack) {
|
|
243
|
+
const stack = project.detected_stack;
|
|
244
|
+
const allDeps = { ...stack.dependencies, ...stack.devDependencies };
|
|
245
|
+
for (const [name, version] of Object.entries(allDeps)) {
|
|
246
|
+
const cleanVersion = version.replace(/[\^~]/g, "");
|
|
247
|
+
if (name === "next" || name === "react" || name === "vue" || name === "angular" || name === "svelte") {
|
|
248
|
+
if (!techStack.framework) {
|
|
249
|
+
techStack.framework = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
250
|
+
}
|
|
251
|
+
} else if (name.includes("prisma") || name.includes("drizzle") || name.includes("typeorm")) {
|
|
252
|
+
if (!techStack.orm) {
|
|
253
|
+
techStack.orm = `${KEY_LIBS[name] || name} ${cleanVersion}`;
|
|
254
|
+
}
|
|
255
|
+
} else if (KEY_LIBS[name]) {
|
|
256
|
+
techStack.keyLibraries.push(`${KEY_LIBS[name]} ${cleanVersion}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (project.repository_tree && Array.isArray(project.repository_tree)) {
|
|
261
|
+
techStack.topFolders = [...new Set(
|
|
262
|
+
project.repository_tree.filter((t) => t.type === "tree" && !t.path.includes("/")).map((t) => t.path)
|
|
263
|
+
)].slice(0, 10);
|
|
264
|
+
}
|
|
265
|
+
const summaryParts = [];
|
|
266
|
+
if (project.project_type) {
|
|
267
|
+
summaryParts.push(`Project Type: ${project.project_type.toUpperCase()}`);
|
|
268
|
+
}
|
|
269
|
+
if (techStack.framework) {
|
|
270
|
+
summaryParts.push(`Framework: ${techStack.framework}`);
|
|
271
|
+
}
|
|
272
|
+
if (techStack.orm) {
|
|
273
|
+
summaryParts.push(`ORM: ${techStack.orm}`);
|
|
274
|
+
}
|
|
275
|
+
if (techStack.keyLibraries.length > 0) {
|
|
276
|
+
summaryParts.push(`Key Libraries: ${techStack.keyLibraries.join(", ")}`);
|
|
277
|
+
}
|
|
278
|
+
if (techStack.topFolders.length > 0) {
|
|
279
|
+
summaryParts.push(`Top Folders: ${techStack.topFolders.join(", ")}`);
|
|
280
|
+
}
|
|
281
|
+
if (project.description) {
|
|
282
|
+
summaryParts.push(`
|
|
283
|
+
Description: ${project.description}`);
|
|
284
|
+
}
|
|
285
|
+
summaryParts.push("\n=== RECENT ACTIVITY DIGEST ===");
|
|
286
|
+
if (agentTasks && agentTasks.length > 0) {
|
|
287
|
+
summaryParts.push("\nLatest AI Executions:");
|
|
288
|
+
agentTasks.forEach((t) => {
|
|
289
|
+
const time = t.completed_at ? new Date(t.completed_at).toLocaleString() : "Recently";
|
|
290
|
+
summaryParts.push(`- [${time}] ${t.roadmap_chunks?.title || "Task"}: ${t.execution_summary || "Completed"}`);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (roadmapItems && roadmapItems.length > 0) {
|
|
294
|
+
summaryParts.push("\nRoadmap Updates:");
|
|
295
|
+
roadmapItems.forEach((i) => {
|
|
296
|
+
summaryParts.push(`- ${i.title} is now ${i.status}`);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
project: {
|
|
301
|
+
id: project.id,
|
|
302
|
+
name: project.name,
|
|
303
|
+
description: project.description,
|
|
304
|
+
projectType: project.project_type,
|
|
305
|
+
createdAt: project.created_at,
|
|
306
|
+
lastIndexedAt: project.last_indexed_at
|
|
307
|
+
},
|
|
308
|
+
techStack,
|
|
309
|
+
summary: summaryParts.join("\n") || "No project context available."
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/tools/query-brain.ts
|
|
314
|
+
async function queryBrain(supabase, userId, projectId, query, limit = 8, threshold = 0.5) {
|
|
315
|
+
const { data: project, error: projectError } = await supabase.from("projects").select("id").eq("id", projectId).eq("owner_id", userId).single();
|
|
316
|
+
if (projectError || !project) {
|
|
317
|
+
throw new Error("Project not found or access denied");
|
|
318
|
+
}
|
|
319
|
+
let memories = [];
|
|
320
|
+
const searchTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
|
|
321
|
+
if (searchTerms.length > 0) {
|
|
322
|
+
const orConditions = searchTerms.map((term) => `content.ilike.%${term}%`).join(",");
|
|
323
|
+
const { data: keywordMatches, error: searchError } = await supabase.from("project_memories").select("id, content, category, tags, net_votes, created_at").eq("project_id", projectId).eq("is_active", true).or(orConditions).order("net_votes", { ascending: false, nullsFirst: false }).limit(limit);
|
|
324
|
+
if (!searchError && keywordMatches) {
|
|
325
|
+
memories = keywordMatches.map((m) => ({
|
|
326
|
+
id: m.id,
|
|
327
|
+
content: m.content,
|
|
328
|
+
category: m.category || "general",
|
|
329
|
+
tags: m.tags || [],
|
|
330
|
+
netVotes: m.net_votes,
|
|
331
|
+
createdAt: m.created_at
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (memories.length === 0) {
|
|
336
|
+
const { data: recentMemories, error: recentError } = await supabase.from("project_memories").select("id, content, category, tags, net_votes, created_at").eq("project_id", projectId).eq("is_active", true).order("created_at", { ascending: false }).limit(limit);
|
|
337
|
+
if (!recentError && recentMemories) {
|
|
338
|
+
memories = recentMemories.map((m) => ({
|
|
339
|
+
id: m.id,
|
|
340
|
+
content: m.content,
|
|
341
|
+
category: m.category || "general",
|
|
342
|
+
tags: m.tags || [],
|
|
343
|
+
netVotes: m.net_votes,
|
|
344
|
+
createdAt: m.created_at
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const contextLines = memories.map((m) => {
|
|
349
|
+
const voteIndicator = m.netVotes && m.netVotes < 0 ? ` [\u26A0\uFE0F POORLY RATED: ${m.netVotes}]` : "";
|
|
350
|
+
const tagStr = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
351
|
+
return `- [${m.category.toUpperCase()}]${tagStr}${voteIndicator}: ${m.content}`;
|
|
352
|
+
});
|
|
353
|
+
const formatted = memories.length > 0 ? `=== PROJECT BRAIN: RELEVANT MEMORIES ===
|
|
354
|
+
Query: "${query}"
|
|
355
|
+
Found ${memories.length} relevant memories:
|
|
356
|
+
|
|
357
|
+
${contextLines.join("\n")}
|
|
358
|
+
|
|
359
|
+
==========================================` : `=== PROJECT BRAIN ===
|
|
360
|
+
Query: "${query}"
|
|
361
|
+
No relevant memories found for this query.
|
|
362
|
+
=======================`;
|
|
363
|
+
return {
|
|
364
|
+
query,
|
|
365
|
+
memories,
|
|
366
|
+
formatted
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/tools/get-latest-decisions.ts
|
|
371
|
+
async function getLatestDecisions(supabase, userId, projectId, limit = 5) {
|
|
372
|
+
const { data: project, error: projectError } = await supabase.from("projects").select("id").eq("id", projectId).eq("owner_id", userId).single();
|
|
373
|
+
if (projectError || !project) {
|
|
374
|
+
throw new Error("Project not found or access denied");
|
|
375
|
+
}
|
|
376
|
+
const { data: sessionData, error: sessionError } = await supabase.from("council_sessions").select("id, project_id, recruited_agents, feedback_summary, duration_ms, sprints_count, tasks_count, created_at").eq("project_id", projectId).order("created_at", { ascending: false }).limit(limit);
|
|
377
|
+
const sessions = (sessionData || []).map((s) => ({
|
|
378
|
+
id: s.id,
|
|
379
|
+
projectId: s.project_id,
|
|
380
|
+
recruitedAgents: s.recruited_agents || [],
|
|
381
|
+
feedbackSummary: (s.feedback_summary || []).map((f) => ({
|
|
382
|
+
agentName: f.agentName || f.agent?.name || "Unknown",
|
|
383
|
+
emoji: f.emoji || f.agent?.emoji || "\u{1F916}",
|
|
384
|
+
critiques: f.critiques || [],
|
|
385
|
+
approved: f.approved ?? true
|
|
386
|
+
})),
|
|
387
|
+
durationMs: s.duration_ms,
|
|
388
|
+
sprintsCount: s.sprints_count,
|
|
389
|
+
tasksCount: s.tasks_count,
|
|
390
|
+
createdAt: s.created_at
|
|
391
|
+
}));
|
|
392
|
+
const { data: activeStep, error: stepError } = await supabase.from("roadmap_chunks").select("id, step_number, title, status, sprint_focus").eq("project_id", projectId).eq("status", "ACTIVE").maybeSingle();
|
|
393
|
+
const activeRoadmapStep = activeStep ? {
|
|
394
|
+
id: activeStep.id,
|
|
395
|
+
stepNumber: activeStep.step_number,
|
|
396
|
+
title: activeStep.title,
|
|
397
|
+
status: activeStep.status,
|
|
398
|
+
sprintFocus: activeStep.sprint_focus
|
|
399
|
+
} : null;
|
|
400
|
+
const summaryParts = [];
|
|
401
|
+
if (activeRoadmapStep) {
|
|
402
|
+
summaryParts.push(`=== CURRENT FOCUS ===`);
|
|
403
|
+
summaryParts.push(`Step ${activeRoadmapStep.stepNumber}: ${activeRoadmapStep.title}`);
|
|
404
|
+
if (activeRoadmapStep.sprintFocus) {
|
|
405
|
+
summaryParts.push(`Focus: ${activeRoadmapStep.sprintFocus}`);
|
|
406
|
+
}
|
|
407
|
+
summaryParts.push("");
|
|
408
|
+
}
|
|
409
|
+
if (sessions.length > 0) {
|
|
410
|
+
summaryParts.push(`=== RECENT COUNCIL SESSIONS (${sessions.length}) ===`);
|
|
411
|
+
for (const session of sessions) {
|
|
412
|
+
const date = new Date(session.createdAt).toLocaleDateString();
|
|
413
|
+
summaryParts.push(`
|
|
414
|
+
\u{1F4C5} Session on ${date}`);
|
|
415
|
+
summaryParts.push(` Agents: ${session.recruitedAgents.join(", ")}`);
|
|
416
|
+
if (session.feedbackSummary.length > 0) {
|
|
417
|
+
summaryParts.push(` Key Feedback:`);
|
|
418
|
+
for (const feedback of session.feedbackSummary.slice(0, 3)) {
|
|
419
|
+
const status = feedback.approved ? "\u2705" : "\u26A0\uFE0F";
|
|
420
|
+
summaryParts.push(` ${feedback.emoji} ${feedback.agentName}: ${status}`);
|
|
421
|
+
if (feedback.critiques.length > 0) {
|
|
422
|
+
summaryParts.push(` - ${feedback.critiques[0]}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
summaryParts.push(`No council sessions recorded yet.`);
|
|
429
|
+
}
|
|
430
|
+
const { data: decisionMemories } = await supabase.from("project_memories").select("content, category, created_at").eq("project_id", projectId).eq("is_active", true).in("category", ["decision", "architecture", "constraint"]).order("created_at", { ascending: false }).limit(5);
|
|
431
|
+
if (decisionMemories && decisionMemories.length > 0) {
|
|
432
|
+
summaryParts.push(`
|
|
433
|
+
=== KEY DECISIONS FROM BRAIN ===`);
|
|
434
|
+
for (const memory of decisionMemories) {
|
|
435
|
+
summaryParts.push(`- [${memory.category.toUpperCase()}] ${memory.content}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
sessions,
|
|
440
|
+
activeRoadmapStep,
|
|
441
|
+
summary: summaryParts.join("\n")
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/resources/project-morals.ts
|
|
446
|
+
import * as fs from "fs";
|
|
447
|
+
import * as path from "path";
|
|
448
|
+
var LOCAL_RULE_FILES = [
|
|
449
|
+
".rigstate/rules.md",
|
|
450
|
+
".rigstate/morals.md",
|
|
451
|
+
".rigstate/standards.md",
|
|
452
|
+
".cursor/rules.md",
|
|
453
|
+
".cursorrules"
|
|
454
|
+
];
|
|
455
|
+
function getProjectMorals(workspacePath) {
|
|
456
|
+
const basePath = workspacePath || process.cwd();
|
|
457
|
+
const rules = [];
|
|
458
|
+
for (const ruleFile of LOCAL_RULE_FILES) {
|
|
459
|
+
const fullPath = path.join(basePath, ruleFile);
|
|
460
|
+
try {
|
|
461
|
+
if (fs.existsSync(fullPath)) {
|
|
462
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
463
|
+
if (content.trim()) {
|
|
464
|
+
rules.push({
|
|
465
|
+
source: ruleFile,
|
|
466
|
+
content: content.trim(),
|
|
467
|
+
priority: ruleFile.includes(".rigstate") ? "high" : "medium"
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} catch (e) {
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
let formatted = "";
|
|
475
|
+
if (rules.length > 0) {
|
|
476
|
+
formatted = `## LOCAL PROJECT MORALS (HIGH PRIORITY)
|
|
477
|
+
`;
|
|
478
|
+
formatted += `These rules are defined in the local workspace and MUST be followed:
|
|
479
|
+
|
|
480
|
+
`;
|
|
481
|
+
for (const rule of rules) {
|
|
482
|
+
formatted += `### Source: ${rule.source}
|
|
483
|
+
`;
|
|
484
|
+
formatted += rule.content;
|
|
485
|
+
formatted += "\n\n";
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
formatted = `No local rules found. You can create .rigstate/rules.md to define project-specific guidelines.`;
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
rules,
|
|
492
|
+
formatted
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// src/tools/check-agent-bridge.ts
|
|
497
|
+
async function checkAgentBridge(supabase, projectId, action = "check", bridgeId, status, summary, execution_summary, proposal) {
|
|
498
|
+
if (action === "submit_for_review") {
|
|
499
|
+
if (!bridgeId) throw new Error("bridgeId is required for submit_for_review action");
|
|
500
|
+
if (!proposal) throw new Error("proposal (with suggestedChanges and reasoning) is required for submission");
|
|
501
|
+
const morals = getProjectMorals();
|
|
502
|
+
const rulesText = morals.formatted.toLowerCase();
|
|
503
|
+
const proposalLower = proposal.toLowerCase();
|
|
504
|
+
if (rulesText.includes("rigstate_start")) {
|
|
505
|
+
if (rulesText.includes("rls") && !proposalLower.includes("rls") && !proposalLower.includes("security")) {
|
|
506
|
+
throw new Error('Validation Failed: Rule "RLS/Security" was violated. Your proposal does not explicitly mention Row Level Security. Please update your proposal to confirm RLS is handled.');
|
|
507
|
+
}
|
|
508
|
+
if (rulesText.includes("strict types") && !proposalLower.includes("type") && !proposalLower.includes("interface")) {
|
|
509
|
+
throw new Error('Validation Failed: Rule "Strict Types" was violated. Your proposal seems to lack TypeScript definitions. Please explicitly plan your types.');
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const { data, error: error2 } = await supabase.from("agent_bridge").update({
|
|
513
|
+
status: "NEEDS_REVIEW",
|
|
514
|
+
proposal,
|
|
515
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
516
|
+
}).eq("id", bridgeId).eq("project_id", projectId).select().single();
|
|
517
|
+
if (error2) throw new Error(`Failed to submit for review: ${error2.message}`);
|
|
518
|
+
return {
|
|
519
|
+
success: true,
|
|
520
|
+
message: `Task ${bridgeId} submitted for review.`,
|
|
521
|
+
task: data
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (action === "update") {
|
|
525
|
+
if (!bridgeId) throw new Error("bridgeId is required for update action");
|
|
526
|
+
if (status === "COMPLETED" || status === "EXECUTING") {
|
|
527
|
+
const { data: currentTask } = await supabase.from("agent_bridge").select("status").eq("id", bridgeId).single();
|
|
528
|
+
if (currentTask && (currentTask.status !== "APPROVED" && currentTask.status !== "EXECUTING")) {
|
|
529
|
+
throw new Error(`Security Violation: Cannot execute or complete task ${bridgeId} because it is in status '${currentTask.status}'. Wait for APPROVED status.`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (!status && !summary && !proposal) throw new Error("At least one of status, summary, or proposal is required for update");
|
|
533
|
+
const updateData = {};
|
|
534
|
+
if (status === "COMPLETED") {
|
|
535
|
+
if (!execution_summary) {
|
|
536
|
+
throw new Error("execution_summary is REQUIRED when setting status to COMPLETED.");
|
|
537
|
+
}
|
|
538
|
+
updateData.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
539
|
+
}
|
|
540
|
+
if (status) updateData.status = status;
|
|
541
|
+
if (summary) updateData.summary = summary;
|
|
542
|
+
if (execution_summary) updateData.execution_summary = execution_summary;
|
|
543
|
+
if (proposal) updateData.proposal = proposal;
|
|
544
|
+
updateData.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
545
|
+
const { data, error: error2 } = await supabase.from("agent_bridge").update(updateData).eq("id", bridgeId).eq("project_id", projectId).select().single();
|
|
546
|
+
if (error2) throw new Error(`Failed to update agent bridge: ${error2.message}`);
|
|
547
|
+
return {
|
|
548
|
+
success: true,
|
|
549
|
+
message: `Updated agent bridge task ${bridgeId} to status ${data.status}`,
|
|
550
|
+
task: data
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const { data: tasks, error } = await supabase.from("agent_bridge").select(`
|
|
554
|
+
*,
|
|
555
|
+
roadmap_chunks (
|
|
556
|
+
title,
|
|
557
|
+
description,
|
|
558
|
+
prompt_content
|
|
559
|
+
)
|
|
560
|
+
`).eq("project_id", projectId).in("status", ["PENDING", "APPROVED", "REJECTED"]).order("created_at", { ascending: true }).limit(1);
|
|
561
|
+
if (error) throw new Error(`Failed to check agent bridge: ${error.message}`);
|
|
562
|
+
if (!tasks || tasks.length === 0) {
|
|
563
|
+
return {
|
|
564
|
+
success: true,
|
|
565
|
+
message: "No pending or approved tasks found."
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
const taskData = tasks[0];
|
|
569
|
+
const task = {
|
|
570
|
+
id: taskData.id,
|
|
571
|
+
project_id: taskData.project_id,
|
|
572
|
+
task_id: taskData.task_id,
|
|
573
|
+
status: taskData.status,
|
|
574
|
+
summary: taskData.summary,
|
|
575
|
+
execution_summary: taskData.execution_summary,
|
|
576
|
+
proposal: taskData.proposal,
|
|
577
|
+
created_at: taskData.created_at,
|
|
578
|
+
completed_at: taskData.completed_at,
|
|
579
|
+
// Map joined data flattener
|
|
580
|
+
task_title: taskData.roadmap_chunks?.title,
|
|
581
|
+
task_description: taskData.roadmap_chunks?.description,
|
|
582
|
+
task_content: taskData.roadmap_chunks?.prompt_content
|
|
583
|
+
};
|
|
584
|
+
return {
|
|
585
|
+
success: true,
|
|
586
|
+
message: `Found task needing attention: ${task.status}`,
|
|
587
|
+
task
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/tools/check-rules-sync.ts
|
|
592
|
+
var RIGSTATE_START = "RIGSTATE_START";
|
|
593
|
+
var RIGSTATE_END = "RIGSTATE_END";
|
|
594
|
+
var SAFETY_CACHE_RULES = `
|
|
595
|
+
### \u{1F6E1}\uFE0F RIGSTATE SAFETY CACHE (OFFLINE MODE)
|
|
596
|
+
1. **Strict Types**: No 'any'. Define interfaces for all data structures.
|
|
597
|
+
2. **Row Level Security**: All database queries MUST be scoped to 'user_id' or 'org_id'.
|
|
598
|
+
3. **Validation**: Use Zod for all input validation.
|
|
599
|
+
4. **Error Handling**: Use try/catch blocks for all external API calls.
|
|
600
|
+
5. **No Blind Deletes**: Never delete data without explicit confirmation or soft-deletes.
|
|
601
|
+
`;
|
|
602
|
+
async function checkRulesSync(supabase, projectId, currentRulesContent) {
|
|
603
|
+
if (!currentRulesContent) {
|
|
604
|
+
return {
|
|
605
|
+
synced: false,
|
|
606
|
+
message: "No rules content provided for verification.",
|
|
607
|
+
shouldTriggerSync: true,
|
|
608
|
+
missingBlock: true
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
const hasStart = currentRulesContent.includes(RIGSTATE_START);
|
|
612
|
+
const hasEnd = currentRulesContent.includes(RIGSTATE_END);
|
|
613
|
+
if (!hasStart || !hasEnd) {
|
|
614
|
+
return {
|
|
615
|
+
synced: false,
|
|
616
|
+
message: "Rigstate rules block is missing or corrupted.",
|
|
617
|
+
shouldTriggerSync: true,
|
|
618
|
+
missingBlock: true
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
const { data: project, error } = await supabase.from("projects").select("name").eq("id", projectId).single();
|
|
623
|
+
if (error) throw error;
|
|
624
|
+
if (project) {
|
|
625
|
+
if (!currentRulesContent.includes(`Project Rules: ${project.name}`)) {
|
|
626
|
+
return {
|
|
627
|
+
synced: false,
|
|
628
|
+
message: `Rules file appears to belong to a different project (Expected: ${project.name}).`,
|
|
629
|
+
shouldTriggerSync: true
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
} catch (e) {
|
|
634
|
+
console.error("Rigstate Sync Verification Failed:", e);
|
|
635
|
+
return {
|
|
636
|
+
synced: true,
|
|
637
|
+
// Assume synced to allow work to continue
|
|
638
|
+
shouldTriggerSync: false,
|
|
639
|
+
offlineMode: true,
|
|
640
|
+
message: `\u26A0\uFE0F Rigstate Sync utilgjengelig \u2013 bruker lokale sikkerhetsregler.
|
|
641
|
+
${SAFETY_CACHE_RULES}`
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
645
|
+
synced: true,
|
|
646
|
+
message: "Rules are valid and present.",
|
|
647
|
+
shouldTriggerSync: false
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/tools/teacher-mode.ts
|
|
652
|
+
import { v4 as uuidv4 } from "uuid";
|
|
653
|
+
async function refineLogic(supabase, userId, projectId, originalReasoning, userCorrection, scope) {
|
|
654
|
+
const traceId = uuidv4();
|
|
655
|
+
const { data: project, error: projectError } = await supabase.from("projects").select("id, name").eq("id", projectId).eq("user_id", userId).single();
|
|
656
|
+
if (projectError || !project) {
|
|
657
|
+
throw new Error(`Project access denied or not found: ${projectId}`);
|
|
658
|
+
}
|
|
659
|
+
const { data: instruction, error: insertError } = await supabase.from("ai_instructions").insert({
|
|
660
|
+
user_id: userId,
|
|
661
|
+
project_id: scope === "global" ? null : projectId,
|
|
662
|
+
instruction: userCorrection,
|
|
663
|
+
context: originalReasoning,
|
|
664
|
+
is_global: scope === "global",
|
|
665
|
+
priority: scope === "global" ? 8 : 6,
|
|
666
|
+
source_log_id: null
|
|
667
|
+
// MCP actions don't have a log ID
|
|
668
|
+
}).select("id").single();
|
|
669
|
+
if (insertError) {
|
|
670
|
+
throw new Error(`Failed to save instruction: ${insertError.message}`);
|
|
671
|
+
}
|
|
672
|
+
await supabase.from("ai_activity_log").insert({
|
|
673
|
+
user_id: userId,
|
|
674
|
+
project_id: projectId,
|
|
675
|
+
activity_type: "mcp_correction",
|
|
676
|
+
summary: `MCP: ${userCorrection.slice(0, 100)}...`,
|
|
677
|
+
intelligence_trace: {
|
|
678
|
+
source: "mcp",
|
|
679
|
+
traceId,
|
|
680
|
+
scope,
|
|
681
|
+
originalReasoning: originalReasoning.slice(0, 500)
|
|
682
|
+
},
|
|
683
|
+
metadata: { mcp_trace_id: traceId }
|
|
684
|
+
});
|
|
685
|
+
return {
|
|
686
|
+
success: true,
|
|
687
|
+
instructionId: instruction.id,
|
|
688
|
+
traceId,
|
|
689
|
+
message: `\u2705 Correction saved! Frank will apply this ${scope === "global" ? "globally" : "to this project"}.`
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
async function getLearnedInstructions(supabase, userId, projectId) {
|
|
693
|
+
let query = supabase.from("ai_instructions").select("instruction, priority, is_global, created_at").eq("user_id", userId).eq("is_active", true).order("priority", { ascending: false }).limit(20);
|
|
694
|
+
if (projectId) {
|
|
695
|
+
query = query.or(`is_global.eq.true,project_id.eq.${projectId}`);
|
|
696
|
+
} else {
|
|
697
|
+
query = query.eq("is_global", true);
|
|
698
|
+
}
|
|
699
|
+
const { data: userInstructions, error: userError } = await query;
|
|
700
|
+
if (userError) {
|
|
701
|
+
throw new Error(`Failed to fetch instructions: ${userError.message}`);
|
|
702
|
+
}
|
|
703
|
+
const { data: globalBase, error: globalError } = await supabase.from("global_base_instructions").select("instruction").eq("is_active", true).order("priority", { ascending: false }).limit(10);
|
|
704
|
+
const globalInstructions = (globalBase || []).map((g) => g.instruction);
|
|
705
|
+
const instructions = (userInstructions || []).map((i) => ({
|
|
706
|
+
instruction: i.instruction,
|
|
707
|
+
priority: i.priority,
|
|
708
|
+
isGlobal: i.is_global,
|
|
709
|
+
createdAt: i.created_at
|
|
710
|
+
}));
|
|
711
|
+
let formatted = "";
|
|
712
|
+
if (globalInstructions.length > 0) {
|
|
713
|
+
formatted += `## GLOBAL RIGSTATE STANDARDS
|
|
714
|
+
`;
|
|
715
|
+
formatted += globalInstructions.map((g) => `- ${g}`).join("\n");
|
|
716
|
+
formatted += "\n\n";
|
|
717
|
+
}
|
|
718
|
+
if (instructions.length > 0) {
|
|
719
|
+
formatted += `## USER CORRECTIONS & TUNING
|
|
720
|
+
`;
|
|
721
|
+
formatted += instructions.map((i) => {
|
|
722
|
+
const scope = i.isGlobal ? "[GLOBAL]" : "[PROJECT]";
|
|
723
|
+
return `- ${scope} ${i.instruction}`;
|
|
724
|
+
}).join("\n");
|
|
725
|
+
}
|
|
726
|
+
if (!formatted) {
|
|
727
|
+
formatted = "No learned behaviors yet. Frank will start learning as you provide corrections.";
|
|
728
|
+
}
|
|
729
|
+
return {
|
|
730
|
+
instructions,
|
|
731
|
+
globalInstructions,
|
|
732
|
+
formatted
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// src/tools/list-roadmap-tasks.ts
|
|
737
|
+
async function listRoadmapTasks(supabase, userId, projectId) {
|
|
738
|
+
const { data: project, error: projectError } = await supabase.from("projects").select("id").eq("id", projectId).eq("owner_id", userId).single();
|
|
739
|
+
if (projectError || !project) {
|
|
740
|
+
throw new Error("Project not found or access denied");
|
|
741
|
+
}
|
|
742
|
+
const { data: tasks, error } = await supabase.from("roadmap_chunks").select("id, title, priority, status, step_number, prompt_content").eq("project_id", projectId).neq("status", "COMPLETED").order("priority", { ascending: false }).order("step_number", { ascending: true });
|
|
743
|
+
if (error) {
|
|
744
|
+
console.error("Failed to fetch roadmap tasks:", error);
|
|
745
|
+
throw new Error("Failed to fetch roadmap tasks");
|
|
746
|
+
}
|
|
747
|
+
const formatted = (tasks || []).length > 0 ? (tasks || []).map((t) => {
|
|
748
|
+
const statusEmoji = t.status === "ACTIVE" ? "\u{1F535}" : "\u{1F512}";
|
|
749
|
+
const priorityStr = t.priority ? `[${t.priority}]` : "";
|
|
750
|
+
return `${statusEmoji} Step ${t.step_number}: ${t.title} (ID: ${t.id})`;
|
|
751
|
+
}).join("\n") : "No active or locked tasks found in the roadmap.";
|
|
752
|
+
return {
|
|
753
|
+
tasks: (tasks || []).map((t) => ({
|
|
754
|
+
id: t.id,
|
|
755
|
+
title: t.title,
|
|
756
|
+
priority: t.priority,
|
|
757
|
+
status: t.status,
|
|
758
|
+
step_number: t.step_number,
|
|
759
|
+
prompt_content: t.prompt_content
|
|
760
|
+
})),
|
|
761
|
+
formatted
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/tools/get-next-roadmap-step.ts
|
|
766
|
+
async function getNextRoadmapStep(supabase, projectId, currentStepId) {
|
|
767
|
+
let currentStepNumber = 0;
|
|
768
|
+
if (currentStepId) {
|
|
769
|
+
const { data: current } = await supabase.from("roadmap_chunks").select("step_number").eq("id", currentStepId).single();
|
|
770
|
+
if (current) {
|
|
771
|
+
currentStepNumber = current.step_number;
|
|
772
|
+
}
|
|
773
|
+
} else {
|
|
774
|
+
const { data: active } = await supabase.from("roadmap_chunks").select("step_number").eq("project_id", projectId).eq("status", "ACTIVE").order("step_number", { ascending: true }).limit(1).single();
|
|
775
|
+
if (active) {
|
|
776
|
+
currentStepNumber = active.step_number;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const { data: nextStep, error } = await supabase.from("roadmap_chunks").select("*").eq("project_id", projectId).gt("step_number", currentStepNumber).neq("status", "COMPLETED").order("step_number", { ascending: true }).limit(1).single();
|
|
780
|
+
if (error && error.code !== "PGRST116") {
|
|
781
|
+
throw new Error(`Failed to fetch next roadmap step: ${error.message}`);
|
|
782
|
+
}
|
|
783
|
+
if (!nextStep) {
|
|
784
|
+
return {
|
|
785
|
+
nextStep: null,
|
|
786
|
+
message: "No further steps found in the roadmap. You might be done! \u{1F389}"
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
nextStep,
|
|
791
|
+
message: `Next step found: [Step ${nextStep.step_number}] ${nextStep.title}`
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/agents/the-scribe.ts
|
|
796
|
+
async function getScribePersona(supabase) {
|
|
797
|
+
const { data: persona, error } = await supabase.from("agent_personas").select("*").eq("slug", "the-scribe").single();
|
|
798
|
+
if (error || !persona) {
|
|
799
|
+
return {
|
|
800
|
+
display_name: "The Scribe",
|
|
801
|
+
job_title: "Technical Documentation Lead",
|
|
802
|
+
content: "You are The Scribe, an expert in technical manifest generation and investor-ready reporting."
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
return persona;
|
|
806
|
+
}
|
|
807
|
+
async function calculateScribeMetrics(supabase, projectId) {
|
|
808
|
+
const { data: recentTasks } = await supabase.from("agent_bridge").select("created_at, updated_at").eq("project_id", projectId).eq("status", "COMPLETED").gte("created_at", new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString());
|
|
809
|
+
let avgTimeMs = 0;
|
|
810
|
+
if (recentTasks && recentTasks.length > 0) {
|
|
811
|
+
const deltas = recentTasks.map(
|
|
812
|
+
(t) => new Date(t.updated_at).getTime() - new Date(t.created_at).getTime()
|
|
813
|
+
);
|
|
814
|
+
avgTimeMs = deltas.reduce((a, b) => a + b, 0) / deltas.length;
|
|
815
|
+
}
|
|
816
|
+
const velocityStr = avgTimeMs > 0 ? `${(avgTimeMs / 1e3).toFixed(1)}s avg execution time` : "No execution history";
|
|
817
|
+
const { count: activeTasksCompleted } = await supabase.from("roadmap_chunks").select("*", { count: "exact", head: true }).eq("project_id", projectId).eq("status", "COMPLETED").or("is_legacy.is.null,is_legacy.eq.false");
|
|
818
|
+
const { count: legacyCount } = await supabase.from("roadmap_chunks").select("*", { count: "exact", head: true }).eq("project_id", projectId).eq("is_legacy", true);
|
|
819
|
+
const { data: trends } = await supabase.from("intelligence_trends").select("correction_count").order("week_start", { ascending: false }).limit(2);
|
|
820
|
+
let trendDir = "stable";
|
|
821
|
+
if (trends && trends.length === 2) {
|
|
822
|
+
const latest = trends[0].correction_count;
|
|
823
|
+
const prev = trends[1].correction_count;
|
|
824
|
+
if (latest < prev) trendDir = "improving";
|
|
825
|
+
else if (latest > prev) trendDir = "declining";
|
|
826
|
+
}
|
|
827
|
+
const qualityStr = `Quality trend is ${trendDir}` + (legacyCount && legacyCount > 0 ? `. Built on ${legacyCount} established foundations.` : "");
|
|
828
|
+
const { count: riskCount } = await supabase.from("agent_bridge").select("*", { count: "exact", head: true }).eq("project_id", projectId).or("proposal.ilike.%PING%,proposal.ilike.%CHECK%,summary.ilike.%PING%,summary.ilike.%CHECK%");
|
|
829
|
+
const { count: rlsCount } = await supabase.from("pg_policies").select("*", { count: "exact", head: true }).eq("schemaname", "public");
|
|
830
|
+
const riskMitigation = (riskCount || 0) > 0 || (rlsCount || 0) > 0 ? `Security posture reinforced by ${rlsCount || 0} RLS policies and ${riskCount || 0} active guardian pings. All access is audited.` : "Security controls inactive. Immediate RLS audit recommended.";
|
|
831
|
+
return {
|
|
832
|
+
velocity: velocityStr,
|
|
833
|
+
qualityTrend: qualityStr,
|
|
834
|
+
riskScore: riskCount || 0,
|
|
835
|
+
riskMitigation,
|
|
836
|
+
activeTasksCompleted: activeTasksCompleted || 0,
|
|
837
|
+
legacyFoundations: legacyCount || 0
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
function interpolateScribePrompt(basePrompt, vars) {
|
|
841
|
+
let result = basePrompt;
|
|
842
|
+
result = result.replace(/{{project_name}}/g, vars.projectName);
|
|
843
|
+
if (vars.velocity) result = result.replace(/{{velocity}}/g, vars.velocity);
|
|
844
|
+
if (vars.quality) result = result.replace(/{{quality}}/g, vars.quality);
|
|
845
|
+
if (vars.riskMitigation) result = result.replace(/{{risk_mitigation}}/g, vars.riskMitigation);
|
|
846
|
+
return result;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/tools/generate-professional-pdf.ts
|
|
850
|
+
async function generateProfessionalPdf(supabase, userId, projectId, reportType) {
|
|
851
|
+
console.error(`\u{1F58B}\uFE0F The Scribe is preparing a ${reportType} briefing for project ${projectId}...`);
|
|
852
|
+
const persona = await getScribePersona(supabase);
|
|
853
|
+
const { data: project } = await supabase.from("projects").select("name, description, project_type, detected_stack").eq("id", projectId).single();
|
|
854
|
+
const projectName = project?.name || "Rigstate Project";
|
|
855
|
+
const projectDescription = project?.description || "A cutting-edge software project built with modern architecture.";
|
|
856
|
+
const projectType = project?.project_type || "Web Application";
|
|
857
|
+
const detectedStack = project?.detected_stack || {};
|
|
858
|
+
const [
|
|
859
|
+
metrics,
|
|
860
|
+
dnaStatsResponse,
|
|
861
|
+
recentDecisions,
|
|
862
|
+
roadmapData,
|
|
863
|
+
councilSessions,
|
|
864
|
+
tableList
|
|
865
|
+
] = await Promise.all([
|
|
866
|
+
calculateScribeMetrics(supabase, projectId),
|
|
867
|
+
supabase.rpc("get_project_dna_stats", { p_project_id: projectId }),
|
|
868
|
+
supabase.from("project_memories").select("summary, category, created_at").eq("project_id", projectId).eq("category", "decision").order("created_at", { ascending: false }).limit(5),
|
|
869
|
+
supabase.from("roadmap_chunks").select("title, status, priority").eq("project_id", projectId).order("priority", { ascending: true }),
|
|
870
|
+
supabase.from("council_sessions").select("executive_summary, created_at").eq("project_id", projectId).order("created_at", { ascending: false }).limit(2),
|
|
871
|
+
supabase.rpc("get_public_tables_list")
|
|
872
|
+
]);
|
|
873
|
+
const dnaStats = dnaStatsResponse.data || { total_tables: 0, rls_tables: 0, rls_policies: 0 };
|
|
874
|
+
const tables = tableList.data || [];
|
|
875
|
+
const roadmapSteps = roadmapData.data || [];
|
|
876
|
+
const allCompletedSteps = roadmapSteps.filter((s) => s.status === "COMPLETED");
|
|
877
|
+
const legacySteps = roadmapSteps.filter((s) => s.is_legacy === true);
|
|
878
|
+
const activeCompletedSteps = allCompletedSteps.filter((s) => s.is_legacy !== true);
|
|
879
|
+
const activeStep = roadmapSteps.find((s) => s.status === "ACTIVE");
|
|
880
|
+
const lockedSteps = roadmapSteps.filter((s) => s.status === "LOCKED");
|
|
881
|
+
const totalNonLegacy = roadmapSteps.filter((s) => s.is_legacy !== true).length;
|
|
882
|
+
const progress = totalNonLegacy > 0 ? Math.round(activeCompletedSteps.length / totalNonLegacy * 100) : 0;
|
|
883
|
+
const deps = detectedStack.dependencies || {};
|
|
884
|
+
const devDeps = detectedStack.devDependencies || {};
|
|
885
|
+
const allDeps = { ...deps, ...devDeps };
|
|
886
|
+
const techStackItems = [];
|
|
887
|
+
if (allDeps["next"]) techStackItems.push(`Next.js ${allDeps["next"]}`);
|
|
888
|
+
if (allDeps["react"]) techStackItems.push(`React ${allDeps["react"]}`);
|
|
889
|
+
if (allDeps["@supabase/supabase-js"]) techStackItems.push("Supabase");
|
|
890
|
+
if (allDeps["tailwindcss"]) techStackItems.push("Tailwind CSS");
|
|
891
|
+
if (allDeps["typescript"]) techStackItems.push("TypeScript");
|
|
892
|
+
if (allDeps["zod"]) techStackItems.push("Zod Validation");
|
|
893
|
+
if (allDeps["@react-pdf/renderer"]) techStackItems.push("React-PDF");
|
|
894
|
+
if (allDeps["sonner"]) techStackItems.push("Sonner Toasts");
|
|
895
|
+
const techStackSummary = techStackItems.length > 0 ? techStackItems.join(" \u2022 ") : "Stack detection pending. Run repository indexing to populate.";
|
|
896
|
+
const adrList = recentDecisions.data?.map((d) => `\u2022 ${d.summary}`) || [];
|
|
897
|
+
const adrContent = adrList.length > 0 ? adrList.join("\n") : "\u2022 No architectural decisions recorded yet. Use the Council or save decisions via chat.";
|
|
898
|
+
const legacyTitles = legacySteps.length > 0 ? `Established Foundations (${legacySteps.length}):
|
|
899
|
+
${legacySteps.map((s) => `\u{1F4DA} ${s.title}`).join("\n")}` : "";
|
|
900
|
+
const activeTitles = activeCompletedSteps.map((s) => `\u2713 ${s.title}`).join("\n");
|
|
901
|
+
const activeTitle = activeStep ? `\u2192 IN PROGRESS: ${activeStep.title}` : "";
|
|
902
|
+
const nextUpTitles = lockedSteps.slice(0, 3).map((s) => `\u25CB ${s.title}`).join("\n");
|
|
903
|
+
const roadmapDetails = [
|
|
904
|
+
legacyTitles,
|
|
905
|
+
activeTitles ? `
|
|
906
|
+
Recent Completions:
|
|
907
|
+
${activeTitles}` : "",
|
|
908
|
+
activeTitle,
|
|
909
|
+
nextUpTitles ? `
|
|
910
|
+
Next Up:
|
|
911
|
+
${nextUpTitles}` : ""
|
|
912
|
+
].filter(Boolean).join("\n");
|
|
913
|
+
const councilInsights = councilSessions.data?.map((s) => s.executive_summary).filter(Boolean).slice(0, 2) || [];
|
|
914
|
+
const councilSummary = councilInsights.length > 0 ? councilInsights.map((i) => `\u2022 ${i}`).join("\n") : "No council sessions recorded yet.";
|
|
915
|
+
const securedTables = dnaStats.rls_tables || 0;
|
|
916
|
+
const totalTables = dnaStats.total_tables || tables.length || 0;
|
|
917
|
+
const rlsPolicies = dnaStats.rls_policies || 0;
|
|
918
|
+
const securityScore = totalTables > 0 ? Math.round(securedTables / totalTables * 100) : 0;
|
|
919
|
+
const securityAnalysis = `
|
|
920
|
+
Security Score: ${securityScore}% of public tables secured with RLS.
|
|
921
|
+
\u2022 Total Public Tables: ${totalTables}
|
|
922
|
+
\u2022 Tables with RLS Enabled: ${securedTables}
|
|
923
|
+
\u2022 Active RLS Policies: ${rlsPolicies}
|
|
924
|
+
\u2022 Guardian Pings (Monitoring): ${metrics.riskScore} detected
|
|
925
|
+
|
|
926
|
+
${securityScore >= 90 ? "\u2713 EXCELLENT: All critical tables are protected." : securityScore >= 70 ? "\u26A0 STABLE: Most tables protected. Review remaining exposures." : "\u26D4 AT RISK: Immediate RLS audit recommended."}
|
|
927
|
+
`.trim();
|
|
928
|
+
const interpolatedPrompt = interpolateScribePrompt(persona.content, {
|
|
929
|
+
projectName,
|
|
930
|
+
velocity: metrics.velocity,
|
|
931
|
+
quality: metrics.qualityTrend,
|
|
932
|
+
riskMitigation: metrics.riskMitigation
|
|
933
|
+
});
|
|
934
|
+
const manifestData = {
|
|
935
|
+
type: "MANIFEST",
|
|
936
|
+
projectName,
|
|
937
|
+
sections: [
|
|
938
|
+
{
|
|
939
|
+
title: "Project Overview",
|
|
940
|
+
content: `${projectDescription}
|
|
941
|
+
|
|
942
|
+
Project Type: ${projectType}
|
|
943
|
+
Tech Stack: ${techStackSummary}`
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
title: "Codebase DNA (Real-time Scan)",
|
|
947
|
+
content: `System consists of ${totalTables} tables with ${rlsPolicies} active RLS security policies.
|
|
948
|
+
|
|
949
|
+
Database integrity is rated as ${securityScore >= 90 ? "EXCELLENT" : securityScore >= 70 ? "STABLE" : "NEEDS ATTENTION"}.
|
|
950
|
+
|
|
951
|
+
Key Tables Detected:
|
|
952
|
+
${tables.slice(0, 10).map((t) => `\u2022 ${t.table_name}`).join("\n") || "\u2022 Run database scan to populate"}`
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
title: "Architectural Decisions (ADRs)",
|
|
956
|
+
content: adrContent
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
title: "Roadmap Execution",
|
|
960
|
+
content: `Development Progress: ${progress}% (${activeCompletedSteps.length} active / ${legacySteps.length} legacy foundations)
|
|
961
|
+
|
|
962
|
+
Velocity: ${metrics.velocity}
|
|
963
|
+
|
|
964
|
+
${roadmapDetails || "No roadmap steps configured yet."}`
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
title: "Council Insights",
|
|
968
|
+
content: councilSummary
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
title: "Security & Risk Assessment",
|
|
972
|
+
content: securityAnalysis
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
title: "Quality & Compliance",
|
|
976
|
+
content: `Quality Trend: ${metrics.qualityTrend}
|
|
977
|
+
|
|
978
|
+
Guard Clauses:
|
|
979
|
+
\u2022 Strict Type Checks (No Any)
|
|
980
|
+
\u2022 Mandatory Supabase RLS Enforcement
|
|
981
|
+
\u2022 Line Limit Enforcement: TS (400) / TSX (250)
|
|
982
|
+
\u2022 All API routes require authentication
|
|
983
|
+
\u2022 CORS policies enforced on public endpoints`
|
|
984
|
+
}
|
|
985
|
+
],
|
|
986
|
+
agent: persona.display_name,
|
|
987
|
+
job_title: persona.job_title,
|
|
988
|
+
metrics: {
|
|
989
|
+
velocity: metrics.velocity,
|
|
990
|
+
qualityTrend: metrics.qualityTrend,
|
|
991
|
+
progress: `${progress}%`,
|
|
992
|
+
securityScore: `${securityScore}%`,
|
|
993
|
+
totalTables,
|
|
994
|
+
rlsPolicies
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
const investorData = {
|
|
998
|
+
type: "INVESTOR",
|
|
999
|
+
projectName,
|
|
1000
|
+
data: {
|
|
1001
|
+
velocity: metrics.velocity,
|
|
1002
|
+
healthScore: Math.min(100, 70 + activeCompletedSteps.length * 3 + legacySteps.length * 2 + progress / 5 + securityScore / 10),
|
|
1003
|
+
qualityTrend: metrics.qualityTrend,
|
|
1004
|
+
roadmapProgress: `${progress}% (${activeCompletedSteps.length} completed, ${legacySteps.length} established foundations)`,
|
|
1005
|
+
projectType,
|
|
1006
|
+
techStack: techStackSummary,
|
|
1007
|
+
keyAchievements: [
|
|
1008
|
+
...activeCompletedSteps.slice(0, 3).map((s) => `Completed: ${s.title}`),
|
|
1009
|
+
...legacySteps.length > 0 ? [`Built on ${legacySteps.length} established foundations`] : [],
|
|
1010
|
+
...recentDecisions.data?.slice(0, 2).map((d) => d.summary) || []
|
|
1011
|
+
].slice(0, 5),
|
|
1012
|
+
currentFocus: activeStep?.title || "Planning next phase",
|
|
1013
|
+
upcomingMilestones: lockedSteps.slice(0, 3).map((s) => s.title),
|
|
1014
|
+
riskMitigation: metrics.riskMitigation,
|
|
1015
|
+
securityPosture: {
|
|
1016
|
+
score: `${securityScore}%`,
|
|
1017
|
+
status: securityScore >= 90 ? "Excellent" : securityScore >= 70 ? "Stable" : "Needs Attention",
|
|
1018
|
+
rlsPolicies,
|
|
1019
|
+
protectedTables: securedTables,
|
|
1020
|
+
totalTables
|
|
1021
|
+
},
|
|
1022
|
+
councilInsights: councilInsights.slice(0, 2)
|
|
1023
|
+
},
|
|
1024
|
+
agent: persona.display_name,
|
|
1025
|
+
job_title: persona.job_title
|
|
1026
|
+
};
|
|
1027
|
+
const reportData = reportType === "SYSTEM_MANIFEST" ? manifestData : investorData;
|
|
1028
|
+
return {
|
|
1029
|
+
success: true,
|
|
1030
|
+
message: `${reportType} briefing is ready.`,
|
|
1031
|
+
data: reportData,
|
|
1032
|
+
debug_prompt_snippet: interpolatedPrompt.substring(0, 500)
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/tools/sync-ide-rules.ts
|
|
1037
|
+
import {
|
|
1038
|
+
generateRuleContent,
|
|
1039
|
+
generateRuleFiles,
|
|
1040
|
+
fetchLegacyStats,
|
|
1041
|
+
fetchActiveAgents,
|
|
1042
|
+
fetchProjectTechStack,
|
|
1043
|
+
getFileNameForIDE
|
|
1044
|
+
} from "@rigstate/rules-engine";
|
|
1045
|
+
async function syncIdeRules(supabase, projectId) {
|
|
1046
|
+
const { data: project, error: projectError } = await supabase.from("projects").select("*").eq("id", projectId).single();
|
|
1047
|
+
if (projectError || !project) {
|
|
1048
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
1049
|
+
}
|
|
1050
|
+
const ide = project.preferred_ide || "cursor";
|
|
1051
|
+
const [stack, roadmapRes, legacyStats, activeAgents] = await Promise.all([
|
|
1052
|
+
fetchProjectTechStack(supabase, projectId),
|
|
1053
|
+
supabase.from("roadmap_chunks").select("step_number, title, status, sprint_focus, prompt_content, is_legacy").eq("project_id", projectId),
|
|
1054
|
+
fetchLegacyStats(supabase, projectId),
|
|
1055
|
+
fetchActiveAgents(supabase)
|
|
1056
|
+
]);
|
|
1057
|
+
const content = generateRuleContent(
|
|
1058
|
+
{ ...project, id: projectId },
|
|
1059
|
+
stack,
|
|
1060
|
+
roadmapRes.data || [],
|
|
1061
|
+
ide,
|
|
1062
|
+
legacyStats,
|
|
1063
|
+
activeAgents
|
|
1064
|
+
);
|
|
1065
|
+
const fileResult = generateRuleFiles(
|
|
1066
|
+
{ ...project, id: projectId },
|
|
1067
|
+
stack,
|
|
1068
|
+
roadmapRes.data || [],
|
|
1069
|
+
ide,
|
|
1070
|
+
legacyStats,
|
|
1071
|
+
activeAgents
|
|
1072
|
+
);
|
|
1073
|
+
return {
|
|
1074
|
+
fileName: getFileNameForIDE(ide),
|
|
1075
|
+
content,
|
|
1076
|
+
files: fileResult.files
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/tools/archaeological-scan.ts
|
|
1081
|
+
function parseGitLog(logOutput) {
|
|
1082
|
+
const commits = [];
|
|
1083
|
+
const entries = logOutput.split("\n---COMMIT---\n").filter(Boolean);
|
|
1084
|
+
for (const entry of entries) {
|
|
1085
|
+
const lines = entry.trim().split("\n");
|
|
1086
|
+
if (lines.length >= 3) {
|
|
1087
|
+
commits.push({
|
|
1088
|
+
hash: lines[0]?.replace("hash:", "").trim() || "",
|
|
1089
|
+
date: lines[1]?.replace("date:", "").trim() || "",
|
|
1090
|
+
author: lines[2]?.replace("author:", "").trim() || "",
|
|
1091
|
+
message: lines.slice(3).join("\n").trim()
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return commits;
|
|
1096
|
+
}
|
|
1097
|
+
function identifyMilestones(commits) {
|
|
1098
|
+
const milestones = [];
|
|
1099
|
+
const featurePatterns = [
|
|
1100
|
+
{ pattern: /\b(auth|authentication|login|signup|oauth)\b/i, category: "Authentication System" },
|
|
1101
|
+
{ pattern: /\b(database|schema|migration|supabase|postgres)\b/i, category: "Database Setup" },
|
|
1102
|
+
{ pattern: /\b(api|endpoint|route)\b/i, category: "API Development" },
|
|
1103
|
+
{ pattern: /\b(ui|component|layout|design|tailwind)\b/i, category: "UI/Component Development" },
|
|
1104
|
+
{ pattern: /\b(test|spec|jest|vitest)\b/i, category: "Testing Infrastructure" },
|
|
1105
|
+
{ pattern: /\b(deploy|ci|cd|github|vercel|docker)\b/i, category: "DevOps & Deployment" },
|
|
1106
|
+
{ pattern: /\b(feature|implement|add|create|build)\b/i, category: "Feature Implementation" },
|
|
1107
|
+
{ pattern: /\b(fix|bug|patch|resolve)\b/i, category: "Bug Fixes & Patches" },
|
|
1108
|
+
{ pattern: /\b(refactor|clean|optimize|improve)\b/i, category: "Code Quality Improvements" },
|
|
1109
|
+
{ pattern: /\b(docs|readme|documentation)\b/i, category: "Documentation" },
|
|
1110
|
+
{ pattern: /\b(config|setup|init|scaffold)\b/i, category: "Project Configuration" },
|
|
1111
|
+
{ pattern: /\b(agent|mcp|ai|llm|openai)\b/i, category: "AI/Agent Integration" },
|
|
1112
|
+
{ pattern: /\b(roadmap|milestone|chunk)\b/i, category: "Roadmap System" },
|
|
1113
|
+
{ pattern: /\b(report|pdf|manifest|governance)\b/i, category: "Reporting & Governance" }
|
|
1114
|
+
];
|
|
1115
|
+
const categoryMap = /* @__PURE__ */ new Map();
|
|
1116
|
+
for (const commit of commits) {
|
|
1117
|
+
for (const { pattern, category } of featurePatterns) {
|
|
1118
|
+
if (pattern.test(commit.message)) {
|
|
1119
|
+
if (!categoryMap.has(category)) {
|
|
1120
|
+
categoryMap.set(category, { commits: [], latestDate: commit.date });
|
|
1121
|
+
}
|
|
1122
|
+
const entry = categoryMap.get(category);
|
|
1123
|
+
entry.commits.push(commit);
|
|
1124
|
+
if (new Date(commit.date) > new Date(entry.latestDate)) {
|
|
1125
|
+
entry.latestDate = commit.date;
|
|
1126
|
+
}
|
|
1127
|
+
break;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
for (const [category, data] of categoryMap.entries()) {
|
|
1132
|
+
if (data.commits.length >= 2) {
|
|
1133
|
+
milestones.push({
|
|
1134
|
+
date: data.latestDate,
|
|
1135
|
+
summary: `${category} (${data.commits.length} commits)`,
|
|
1136
|
+
commits: data.commits.slice(0, 5).map((c) => c.message.split("\n")[0].substring(0, 80))
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
milestones.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
1141
|
+
return milestones;
|
|
1142
|
+
}
|
|
1143
|
+
function analyzeFilesystem(tree) {
|
|
1144
|
+
const featurePatterns = [
|
|
1145
|
+
/^(apps|packages)\/[^/]+\/src\/(components|features|modules)\/[^/]+$/,
|
|
1146
|
+
/^(apps|packages)\/[^/]+\/src\/app\/[^/]+$/,
|
|
1147
|
+
/^src\/(components|features|modules|pages)\/[^/]+$/,
|
|
1148
|
+
/^(apps|packages)\/[^/]+$/
|
|
1149
|
+
];
|
|
1150
|
+
const configPatterns = [
|
|
1151
|
+
/package\.json$/,
|
|
1152
|
+
/tsconfig.*\.json$/,
|
|
1153
|
+
/\.env.*$/,
|
|
1154
|
+
/next\.config\./,
|
|
1155
|
+
/tailwind\.config\./,
|
|
1156
|
+
/supabase.*\.toml$/
|
|
1157
|
+
];
|
|
1158
|
+
const featureDirectories = tree.filter(
|
|
1159
|
+
(path5) => featurePatterns.some((pattern) => pattern.test(path5))
|
|
1160
|
+
);
|
|
1161
|
+
const configFiles = tree.filter(
|
|
1162
|
+
(path5) => configPatterns.some((pattern) => pattern.test(path5))
|
|
1163
|
+
);
|
|
1164
|
+
return {
|
|
1165
|
+
featureDirectories: [...new Set(featureDirectories)].slice(0, 20),
|
|
1166
|
+
configFiles: [...new Set(configFiles)].slice(0, 10)
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
function generateDiscoveredFeatures(milestones, filesystemAnalysis) {
|
|
1170
|
+
const features = [];
|
|
1171
|
+
let priority = 1;
|
|
1172
|
+
for (const milestone of milestones.slice(0, 10)) {
|
|
1173
|
+
const id = `ghost-${Date.now()}-${priority}`;
|
|
1174
|
+
features.push({
|
|
1175
|
+
id,
|
|
1176
|
+
title: milestone.summary.split("(")[0].trim(),
|
|
1177
|
+
description: `Reconstructed from ${milestone.commits.length} commits. Last activity: ${new Date(milestone.date).toLocaleDateString()}`,
|
|
1178
|
+
status: "COMPLETED",
|
|
1179
|
+
source: "git",
|
|
1180
|
+
evidence: milestone.commits,
|
|
1181
|
+
estimatedCompletionDate: milestone.date,
|
|
1182
|
+
priority: priority++
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
const directoryFeatures = filesystemAnalysis.featureDirectories.filter((dir) => dir.includes("src/") || dir.startsWith("apps/") || dir.startsWith("packages/")).slice(0, 5);
|
|
1186
|
+
for (const dir of directoryFeatures) {
|
|
1187
|
+
const name = dir.split("/").pop() || dir;
|
|
1188
|
+
const id = `ghost-fs-${Date.now()}-${priority}`;
|
|
1189
|
+
if (features.some((f) => f.title.toLowerCase().includes(name.toLowerCase()))) {
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
features.push({
|
|
1193
|
+
id,
|
|
1194
|
+
title: `${name.charAt(0).toUpperCase() + name.slice(1)} Module`,
|
|
1195
|
+
description: `Detected from directory structure: ${dir}`,
|
|
1196
|
+
status: "COMPLETED",
|
|
1197
|
+
source: "filesystem",
|
|
1198
|
+
evidence: [dir],
|
|
1199
|
+
estimatedCompletionDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1200
|
+
priority: priority++
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
return features;
|
|
1204
|
+
}
|
|
1205
|
+
async function performArchaeologicalScan(supabase, projectId, gitLog, fileTree) {
|
|
1206
|
+
console.error(`\u{1F3DB}\uFE0F Brynjar is performing an archaeological scan for project ${projectId}...`);
|
|
1207
|
+
const commits = parseGitLog(gitLog);
|
|
1208
|
+
const milestones = identifyMilestones(commits);
|
|
1209
|
+
const filesystemAnalysis = analyzeFilesystem(fileTree);
|
|
1210
|
+
const discoveredFeatures = generateDiscoveredFeatures(milestones, filesystemAnalysis);
|
|
1211
|
+
const recommendations = [];
|
|
1212
|
+
if (milestones.length === 0) {
|
|
1213
|
+
recommendations.push("No significant milestones detected. Consider adding more descriptive commit messages.");
|
|
1214
|
+
}
|
|
1215
|
+
if (filesystemAnalysis.featureDirectories.length < 3) {
|
|
1216
|
+
recommendations.push("Project structure appears simple. This may be intentional or early-stage.");
|
|
1217
|
+
}
|
|
1218
|
+
if (discoveredFeatures.length > 0) {
|
|
1219
|
+
recommendations.push(`${discoveredFeatures.length} ghost features ready for import into the roadmap.`);
|
|
1220
|
+
}
|
|
1221
|
+
if (!filesystemAnalysis.configFiles.some((f) => f.includes("supabase"))) {
|
|
1222
|
+
recommendations.push("No Supabase configuration detected. Database integration may be pending.");
|
|
1223
|
+
}
|
|
1224
|
+
const report = {
|
|
1225
|
+
projectId,
|
|
1226
|
+
scanDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1227
|
+
gitAnalysis: {
|
|
1228
|
+
totalCommits: commits.length,
|
|
1229
|
+
analyzedCommits: Math.min(commits.length, 100),
|
|
1230
|
+
milestones
|
|
1231
|
+
},
|
|
1232
|
+
filesystemAnalysis: {
|
|
1233
|
+
totalDirectories: fileTree.filter((f) => !f.includes(".")).length,
|
|
1234
|
+
...filesystemAnalysis
|
|
1235
|
+
},
|
|
1236
|
+
discoveredFeatures,
|
|
1237
|
+
recommendations
|
|
1238
|
+
};
|
|
1239
|
+
console.error(`\u{1F3DB}\uFE0F Brynjar found ${discoveredFeatures.length} ghost features and ${milestones.length} milestones.`);
|
|
1240
|
+
return report;
|
|
1241
|
+
}
|
|
1242
|
+
async function importGhostFeatures(supabase, projectId, features) {
|
|
1243
|
+
console.error(`\u{1F3DB}\uFE0F Brynjar is importing ${features.length} ghost features into the roadmap...`);
|
|
1244
|
+
const errors = [];
|
|
1245
|
+
let imported = 0;
|
|
1246
|
+
for (const feature of features) {
|
|
1247
|
+
const { error } = await supabase.from("roadmap_chunks").insert({
|
|
1248
|
+
project_id: projectId,
|
|
1249
|
+
title: feature.title,
|
|
1250
|
+
description: feature.description,
|
|
1251
|
+
status: "COMPLETED",
|
|
1252
|
+
priority: feature.priority,
|
|
1253
|
+
is_legacy: true,
|
|
1254
|
+
// Mark as reconstructed historical feature
|
|
1255
|
+
completed_at: feature.estimatedCompletionDate,
|
|
1256
|
+
// Original completion date from Git
|
|
1257
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1258
|
+
// Import timestamp
|
|
1259
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1260
|
+
});
|
|
1261
|
+
if (error) {
|
|
1262
|
+
errors.push(`Failed to import "${feature.title}": ${error.message}`);
|
|
1263
|
+
} else {
|
|
1264
|
+
imported++;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
console.error(`\u{1F3DB}\uFE0F Brynjar imported ${imported}/${features.length} ghost features.`);
|
|
1268
|
+
return { success: errors.length === 0, imported, errors };
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// src/tools/arch-tools.ts
|
|
1272
|
+
import { promises as fs2 } from "fs";
|
|
1273
|
+
import * as path2 from "path";
|
|
1274
|
+
async function analyzeDependencyGraph(input) {
|
|
1275
|
+
const searchPath = path2.isAbsolute(input.path) ? input.path : path2.resolve(process.cwd(), input.path);
|
|
1276
|
+
try {
|
|
1277
|
+
await fs2.access(searchPath);
|
|
1278
|
+
} catch {
|
|
1279
|
+
return {
|
|
1280
|
+
error: `Directory not found: ${searchPath}. Ensure you are running the MCP server in the project root or provide an absolute path.`
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
const allFiles = await getAllFiles(searchPath);
|
|
1284
|
+
const tsFiles = allFiles.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f) && !f.includes("node_modules") && !f.includes(".next") && !f.includes("dist"));
|
|
1285
|
+
const graph = {};
|
|
1286
|
+
const fileSet = new Set(tsFiles);
|
|
1287
|
+
for (const file of tsFiles) {
|
|
1288
|
+
const content = await fs2.readFile(file, "utf-8");
|
|
1289
|
+
const imports = extractImports(content);
|
|
1290
|
+
const validDeps = [];
|
|
1291
|
+
for (const imp of imports) {
|
|
1292
|
+
const resolved = resolveImport(file, imp, searchPath);
|
|
1293
|
+
if (resolved && fileSet.has(resolved)) {
|
|
1294
|
+
validDeps.push(resolved);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
const relFile = path2.relative(searchPath, file);
|
|
1298
|
+
graph[relFile] = validDeps.map((d) => path2.relative(searchPath, d));
|
|
1299
|
+
}
|
|
1300
|
+
const cycles = detectCycles(graph);
|
|
1301
|
+
return {
|
|
1302
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1303
|
+
analyzedPath: searchPath,
|
|
1304
|
+
metrics: {
|
|
1305
|
+
totalFiles: tsFiles.length,
|
|
1306
|
+
circularDependencies: cycles.length
|
|
1307
|
+
},
|
|
1308
|
+
cycles,
|
|
1309
|
+
status: cycles.length > 0 ? "VIOLATION" : "PASS",
|
|
1310
|
+
summary: cycles.length > 0 ? `FAILED. Detected ${cycles.length} circular dependencies. These must be resolved to maintain architectural integrity.` : `PASSED. No circular dependencies detected in ${tsFiles.length} files.`
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
async function getAllFiles(dir) {
|
|
1314
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
1315
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
1316
|
+
const res = path2.resolve(dir, entry.name);
|
|
1317
|
+
return entry.isDirectory() ? getAllFiles(res) : res;
|
|
1318
|
+
}));
|
|
1319
|
+
return files.flat();
|
|
1320
|
+
}
|
|
1321
|
+
function extractImports(content) {
|
|
1322
|
+
const regex = /from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/g;
|
|
1323
|
+
const imports = [];
|
|
1324
|
+
let match;
|
|
1325
|
+
while ((match = regex.exec(content)) !== null) {
|
|
1326
|
+
imports.push(match[1] || match[2]);
|
|
1327
|
+
}
|
|
1328
|
+
return imports;
|
|
1329
|
+
}
|
|
1330
|
+
function resolveImport(importer, importPath, root) {
|
|
1331
|
+
if (!importPath.startsWith(".") && !importPath.startsWith("@/")) {
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1334
|
+
let searchDir = path2.dirname(importer);
|
|
1335
|
+
let target = importPath;
|
|
1336
|
+
if (importPath.startsWith("@/")) {
|
|
1337
|
+
target = importPath.replace("@/", "");
|
|
1338
|
+
searchDir = root;
|
|
1339
|
+
}
|
|
1340
|
+
const startPath = path2.resolve(searchDir, target);
|
|
1341
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", ""];
|
|
1342
|
+
for (const ext of extensions) {
|
|
1343
|
+
const candidate = startPath + ext;
|
|
1344
|
+
if (__require("fs").existsSync(candidate) && !__require("fs").statSync(candidate).isDirectory()) {
|
|
1345
|
+
return candidate;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
function detectCycles(graph) {
|
|
1351
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1352
|
+
const recursionStack = /* @__PURE__ */ new Set();
|
|
1353
|
+
const cycles = [];
|
|
1354
|
+
function dfs(node, path5) {
|
|
1355
|
+
visited.add(node);
|
|
1356
|
+
recursionStack.add(node);
|
|
1357
|
+
path5.push(node);
|
|
1358
|
+
const deps = graph[node] || [];
|
|
1359
|
+
for (const dep of deps) {
|
|
1360
|
+
if (!visited.has(dep)) {
|
|
1361
|
+
dfs(dep, path5);
|
|
1362
|
+
} else if (recursionStack.has(dep)) {
|
|
1363
|
+
const cycleStart = path5.indexOf(dep);
|
|
1364
|
+
if (cycleStart !== -1) {
|
|
1365
|
+
cycles.push([...path5.slice(cycleStart), dep]);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
recursionStack.delete(node);
|
|
1370
|
+
path5.pop();
|
|
1371
|
+
}
|
|
1372
|
+
for (const node of Object.keys(graph)) {
|
|
1373
|
+
if (!visited.has(node)) {
|
|
1374
|
+
dfs(node, []);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return cycles;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// src/tools/security-tools.ts
|
|
1381
|
+
async function auditRlsStatus(supabase, input) {
|
|
1382
|
+
try {
|
|
1383
|
+
const { data, error } = await supabase.rpc("execute_sql", {
|
|
1384
|
+
query: `
|
|
1385
|
+
SELECT tablename, rowsecurity
|
|
1386
|
+
FROM pg_tables
|
|
1387
|
+
WHERE schemaname = 'public'
|
|
1388
|
+
ORDER BY tablename;
|
|
1389
|
+
`
|
|
1390
|
+
});
|
|
1391
|
+
if (error) {
|
|
1392
|
+
throw new Error(`Database query failed: ${error.message}`);
|
|
1393
|
+
}
|
|
1394
|
+
const tables = data;
|
|
1395
|
+
if (!tables) {
|
|
1396
|
+
return {
|
|
1397
|
+
status: "ERROR",
|
|
1398
|
+
message: "Could not fetch table list. Ensure execute_sql RPC is available or pg_tables is accessible."
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
const unsecuredTables = tables.filter((t) => !t.rowsecurity);
|
|
1402
|
+
const securedTables = tables.filter((t) => t.rowsecurity);
|
|
1403
|
+
const score = tables.length > 0 ? Math.round(securedTables.length / tables.length * 100) : 100;
|
|
1404
|
+
return {
|
|
1405
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1406
|
+
projectId: input.projectId,
|
|
1407
|
+
securityScore: score,
|
|
1408
|
+
metrics: {
|
|
1409
|
+
totalTables: tables.length,
|
|
1410
|
+
securedTables: securedTables.length,
|
|
1411
|
+
unsecuredTables: unsecuredTables.length
|
|
1412
|
+
},
|
|
1413
|
+
unsecuredTables: unsecuredTables.map((t) => t.tablename),
|
|
1414
|
+
status: unsecuredTables.length === 0 ? "SECURE" : "VULNERABLE",
|
|
1415
|
+
summary: unsecuredTables.length === 0 ? `PASSED. All ${tables.length} tables have RLS enabled. Sven approves.` : `CRITICAL. ${unsecuredTables.length} tables are missing RLS: ${unsecuredTables.map((t) => t.tablename).join(", ")}. Immediate action required.`
|
|
1416
|
+
};
|
|
1417
|
+
} catch (err) {
|
|
1418
|
+
return {
|
|
1419
|
+
status: "ERROR",
|
|
1420
|
+
message: `Sven could not reach the database structure. Error: ${err.message || err}. check connection strings.`
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// src/tools/research-tools.ts
|
|
1426
|
+
async function queryProjectBrain(supabase, userId, input) {
|
|
1427
|
+
const { projectId, query, limit = 5 } = input;
|
|
1428
|
+
const { data: project, error: pError } = await supabase.from("projects").select("id").eq("id", projectId).eq("owner_id", userId).single();
|
|
1429
|
+
if (pError || !project) throw new Error("Access denied or project not found");
|
|
1430
|
+
const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
|
|
1431
|
+
let queryBuilder = supabase.from("project_memories").select("id, content, category, tags, importance, created_at").eq("project_id", projectId).eq("is_active", true);
|
|
1432
|
+
if (terms.length > 0) {
|
|
1433
|
+
const orParams = terms.map((t) => `content.ilike.%${t}%`).join(",");
|
|
1434
|
+
queryBuilder = queryBuilder.or(orParams);
|
|
1435
|
+
}
|
|
1436
|
+
const { data, error } = await queryBuilder.order("importance", { ascending: false }).order("created_at", { ascending: false }).limit(limit);
|
|
1437
|
+
if (error) throw new Error(`Brain query failed: ${error.message}`);
|
|
1438
|
+
const results = (data || []).map((m) => ({
|
|
1439
|
+
id: m.id,
|
|
1440
|
+
category: m.category,
|
|
1441
|
+
tags: m.tags,
|
|
1442
|
+
importance: m.importance,
|
|
1443
|
+
preview: m.content.substring(0, 200) + "...",
|
|
1444
|
+
fullContent: m.content,
|
|
1445
|
+
date: new Date(m.created_at).toLocaleDateString()
|
|
1446
|
+
}));
|
|
1447
|
+
return {
|
|
1448
|
+
query,
|
|
1449
|
+
matchCount: results.length,
|
|
1450
|
+
results
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
async function fetchPackageHealth(input) {
|
|
1454
|
+
const { packageName } = input;
|
|
1455
|
+
try {
|
|
1456
|
+
const res = await fetch(`https://registry.npmjs.org/${packageName}`);
|
|
1457
|
+
if (!res.ok) {
|
|
1458
|
+
if (res.status === 404) return { status: "NOT_FOUND", message: "Package does not exist." };
|
|
1459
|
+
throw new Error(`Registry error: ${res.statusText}`);
|
|
1460
|
+
}
|
|
1461
|
+
const data = await res.json();
|
|
1462
|
+
const latestVersion = data["dist-tags"]?.latest;
|
|
1463
|
+
if (!latestVersion) throw new Error("No latest version found");
|
|
1464
|
+
const versionData = data.versions[latestVersion];
|
|
1465
|
+
const lastPublish = data.time[latestVersion];
|
|
1466
|
+
const dlRes = await fetch(`https://api.npmjs.org/downloads/point/last-week/${packageName}`);
|
|
1467
|
+
let downloads = 0;
|
|
1468
|
+
if (dlRes.ok) {
|
|
1469
|
+
const dlData = await dlRes.json();
|
|
1470
|
+
downloads = dlData.downloads || 0;
|
|
1471
|
+
}
|
|
1472
|
+
const lastDate = new Date(lastPublish);
|
|
1473
|
+
const now = /* @__PURE__ */ new Date();
|
|
1474
|
+
const ageCodeDays = (now.getTime() - lastDate.getTime()) / (1e3 * 3600 * 24);
|
|
1475
|
+
let healthScore = "MODERATE";
|
|
1476
|
+
let recommendation = "Usable, but verify.";
|
|
1477
|
+
let riskFactors = [];
|
|
1478
|
+
if (data.deprecated) {
|
|
1479
|
+
healthScore = "CRITICAL";
|
|
1480
|
+
recommendation = "DO NOT USE. Package is deprecated.";
|
|
1481
|
+
riskFactors.push(`Deprecated: ${data.deprecated}`);
|
|
1482
|
+
}
|
|
1483
|
+
if (ageCodeDays > 730) {
|
|
1484
|
+
healthScore = "STAGNANT";
|
|
1485
|
+
recommendation = "Consider alternatives. No updates in 2+ years.";
|
|
1486
|
+
riskFactors.push("Mainteinance mode? (Old)");
|
|
1487
|
+
}
|
|
1488
|
+
if (downloads < 50) {
|
|
1489
|
+
healthScore = "LOW_ADOPTION";
|
|
1490
|
+
riskFactors.push("Very low downloads (<50/week)");
|
|
1491
|
+
if (healthScore !== "CRITICAL") recommendation = "High risk. Barely used.";
|
|
1492
|
+
} else if (downloads > 1e5 && ageCodeDays < 180) {
|
|
1493
|
+
healthScore = "EXCELLENT";
|
|
1494
|
+
recommendation = "Strongly recommended. Industry standard.";
|
|
1495
|
+
} else if (downloads > 5e3 && ageCodeDays < 365) {
|
|
1496
|
+
healthScore = "HEALTHY";
|
|
1497
|
+
recommendation = "Safe to use.";
|
|
1498
|
+
}
|
|
1499
|
+
return {
|
|
1500
|
+
packageName,
|
|
1501
|
+
latestVersion,
|
|
1502
|
+
lastPublished: lastPublish,
|
|
1503
|
+
downloadsLastWeek: downloads,
|
|
1504
|
+
license: versionData.license || "Unknown",
|
|
1505
|
+
description: data.description,
|
|
1506
|
+
healthScore,
|
|
1507
|
+
recommendation,
|
|
1508
|
+
riskFactors,
|
|
1509
|
+
maintainers: data.maintainers?.length || 0
|
|
1510
|
+
};
|
|
1511
|
+
} catch (e) {
|
|
1512
|
+
return {
|
|
1513
|
+
status: "ERROR",
|
|
1514
|
+
message: `Analysis failed: ${e.message}`
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/tools/complete-roadmap-task.ts
|
|
1520
|
+
async function completeRoadmapTask(supabase, projectId, summary, taskId, gitDiff) {
|
|
1521
|
+
let targetTaskId = taskId;
|
|
1522
|
+
if (!targetTaskId) {
|
|
1523
|
+
const { data: activeTask } = await supabase.from("roadmap_chunks").select("id, title").eq("project_id", projectId).in("status", ["IN_PROGRESS", "ACTIVE"]).order("step_number", { ascending: true }).limit(1).single();
|
|
1524
|
+
if (activeTask) {
|
|
1525
|
+
targetTaskId = activeTask.id;
|
|
1526
|
+
} else {
|
|
1527
|
+
throw new Error("No active task found to complete. Please provide a specific taskId.");
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
const { error: updateError } = await supabase.from("roadmap_chunks").update({
|
|
1531
|
+
status: "COMPLETED",
|
|
1532
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1533
|
+
// We could store the summary directly on the chunk if there's a column,
|
|
1534
|
+
// but mission_reports is the proper place for detailed logs.
|
|
1535
|
+
// Let's check if we can update metadata or similar.
|
|
1536
|
+
// For now, let's assume mission_reports is the way.
|
|
1537
|
+
}).eq("id", targetTaskId);
|
|
1538
|
+
if (updateError) {
|
|
1539
|
+
throw new Error(`Failed to update task status: ${updateError.message}`);
|
|
1540
|
+
}
|
|
1541
|
+
const { error: reportError } = await supabase.from("mission_reports").insert({
|
|
1542
|
+
project_id: projectId,
|
|
1543
|
+
task_id: targetTaskId,
|
|
1544
|
+
human_summary: summary,
|
|
1545
|
+
technical_summary: gitDiff || "Completed via IDE Direct Connection."
|
|
1546
|
+
// bridge_id: null // Assuming nullable
|
|
1547
|
+
});
|
|
1548
|
+
if (reportError) {
|
|
1549
|
+
console.warn("Failed to save mission report:", reportError.message);
|
|
1550
|
+
}
|
|
1551
|
+
return {
|
|
1552
|
+
success: true,
|
|
1553
|
+
taskId: targetTaskId,
|
|
1554
|
+
message: `Task completed successfully! Summary saved.`
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// src/tools/list-features.ts
|
|
1559
|
+
import { z as z2 } from "zod";
|
|
1560
|
+
var InputSchema = z2.object({
|
|
1561
|
+
projectId: z2.string().uuid().describe("The UUID of the Rigstate project")
|
|
1562
|
+
});
|
|
1563
|
+
var listFeaturesTool = {
|
|
1564
|
+
name: "list_features",
|
|
1565
|
+
description: `Lists all high-level features (epics) for a project.
|
|
1566
|
+
Useful for understanding the strategic context and major milestones.`,
|
|
1567
|
+
schema: InputSchema,
|
|
1568
|
+
handler: async ({ projectId }, { supabase, userId }) => {
|
|
1569
|
+
const { data: project, error: projectError } = await supabase.from("projects").select("id").eq("id", projectId).eq("owner_id", userId).single();
|
|
1570
|
+
if (projectError || !project) {
|
|
1571
|
+
throw new Error("Project not found or access denied");
|
|
1572
|
+
}
|
|
1573
|
+
const { data: features, error } = await supabase.from("features").select("id, name, description, priority, status").eq("project_id", projectId).neq("status", "ARCHIVED").order("created_at", { ascending: false });
|
|
1574
|
+
if (error) {
|
|
1575
|
+
throw new Error(`Failed to fetch features: ${error.message}`);
|
|
1576
|
+
}
|
|
1577
|
+
const formatted = (features || []).length > 0 ? (features || []).map((f) => {
|
|
1578
|
+
const priorityStr = f.priority === "MVP" ? "[MVP] " : "";
|
|
1579
|
+
return `- ${priorityStr}${f.name} (${f.status})`;
|
|
1580
|
+
}).join("\n") : "No active features found.";
|
|
1581
|
+
return {
|
|
1582
|
+
content: [
|
|
1583
|
+
{
|
|
1584
|
+
type: "text",
|
|
1585
|
+
text: formatted
|
|
1586
|
+
}
|
|
1587
|
+
]
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
// src/lib/tool-registry.ts
|
|
1593
|
+
import { z as z3 } from "zod";
|
|
1594
|
+
var ToolRegistry = class {
|
|
1595
|
+
tools = /* @__PURE__ */ new Map();
|
|
1596
|
+
/**
|
|
1597
|
+
* Registers a tool logic with the system.
|
|
1598
|
+
*/
|
|
1599
|
+
register(tool) {
|
|
1600
|
+
if (this.tools.has(tool.name)) {
|
|
1601
|
+
console.warn(`Tool '${tool.name}' is already registered. Overwriting.`);
|
|
1602
|
+
}
|
|
1603
|
+
this.tools.set(tool.name, tool);
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Returns the list of tools formatted for the MCP 'ListTools' request.
|
|
1607
|
+
* Converts Zod schemas to JSON Schemas implicitly via manual type mapping or libraries.
|
|
1608
|
+
* For now, we manually map 'inputSchema' as 'object' in the index.ts,
|
|
1609
|
+
* but here we prepare the definitions.
|
|
1610
|
+
*/
|
|
1611
|
+
getTools() {
|
|
1612
|
+
return Array.from(this.tools.values()).map((tool) => {
|
|
1613
|
+
return {
|
|
1614
|
+
name: tool.name,
|
|
1615
|
+
description: tool.description,
|
|
1616
|
+
inputSchema: this.zodToJsonSchema(tool.schema)
|
|
1617
|
+
};
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Executes a tool by name.
|
|
1622
|
+
* Validates input against schema before execution.
|
|
1623
|
+
*/
|
|
1624
|
+
async callTool(name, args, context) {
|
|
1625
|
+
const tool = this.tools.get(name);
|
|
1626
|
+
if (!tool) {
|
|
1627
|
+
throw new Error(`Tool '${name}' not found.`);
|
|
1628
|
+
}
|
|
1629
|
+
const parseResult = tool.schema.safeParse(args);
|
|
1630
|
+
if (!parseResult.success) {
|
|
1631
|
+
throw new Error(`Invalid arguments for tool '${name}': ${parseResult.error.message}`);
|
|
1632
|
+
}
|
|
1633
|
+
return await tool.handler(parseResult.data, context);
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Basic helper to convert Zod Object to JSON Schema.
|
|
1637
|
+
* Note: This is a simplified version. For complex types, use 'zod-to-json-schema'.
|
|
1638
|
+
*/
|
|
1639
|
+
zodToJsonSchema(schema) {
|
|
1640
|
+
if (schema instanceof z3.ZodObject) {
|
|
1641
|
+
const shape = schema.shape;
|
|
1642
|
+
const properties = {};
|
|
1643
|
+
const required = [];
|
|
1644
|
+
for (const key in shape) {
|
|
1645
|
+
const field = shape[key];
|
|
1646
|
+
let type = "string";
|
|
1647
|
+
let description = field.description;
|
|
1648
|
+
if (field instanceof z3.ZodNumber) type = "number";
|
|
1649
|
+
if (field instanceof z3.ZodBoolean) type = "boolean";
|
|
1650
|
+
if (field instanceof z3.ZodArray) type = "array";
|
|
1651
|
+
if (!field.isOptional()) {
|
|
1652
|
+
required.push(key);
|
|
1653
|
+
}
|
|
1654
|
+
properties[key] = { type, description };
|
|
1655
|
+
}
|
|
1656
|
+
return {
|
|
1657
|
+
type: "object",
|
|
1658
|
+
properties,
|
|
1659
|
+
required
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
return { type: "object", properties: {} };
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1665
|
+
var registry = new ToolRegistry();
|
|
1666
|
+
|
|
1667
|
+
// src/tools/ui-tools.ts
|
|
1668
|
+
import fs3 from "fs/promises";
|
|
1669
|
+
import path3 from "path";
|
|
1670
|
+
async function analyzeUiComponent(input) {
|
|
1671
|
+
const { filePath } = input;
|
|
1672
|
+
if (!await fileExists(filePath)) {
|
|
1673
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1674
|
+
}
|
|
1675
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
1676
|
+
const issues = [];
|
|
1677
|
+
const score = 100;
|
|
1678
|
+
const hexRegex = /#([0-9A-Fa-f]{3}){1,2}\b/g;
|
|
1679
|
+
const hexMatches = content.match(hexRegex);
|
|
1680
|
+
if (hexMatches) {
|
|
1681
|
+
const isCss = filePath.endsWith(".css");
|
|
1682
|
+
const uniqueHex = [...new Set(hexMatches)];
|
|
1683
|
+
for (const hex of uniqueHex) {
|
|
1684
|
+
if (!isCss || content.includes(`color: ${hex}`) || content.includes(`background: ${hex}`)) {
|
|
1685
|
+
issues.push(`Hardcoded color found: ${hex}. Use design tokens (e.g., bg-background, text-primary).`);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
if (filePath.endsWith(".tsx")) {
|
|
1690
|
+
const imgRegex = /<img(?![^>]*\balt=)[^>]*>/g;
|
|
1691
|
+
if (imgRegex.test(content)) {
|
|
1692
|
+
issues.push('Accessibility: <img> tag detected without "alt" attribute.');
|
|
1693
|
+
}
|
|
1694
|
+
const nextImgRegex = /<Image(?![^>]*\balt=)[^>]*>/g;
|
|
1695
|
+
if (nextImgRegex.test(content)) {
|
|
1696
|
+
issues.push('Accessibility: <Image> component missing "alt" prop.');
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
const arbitraryRegex = /\w-\[\d+px\]/g;
|
|
1700
|
+
if (arbitraryRegex.test(content)) {
|
|
1701
|
+
issues.push("maintainability: Arbitrary pixel values detected (e.g., w-[50px]). Use standard spacing scale (w-12).");
|
|
1702
|
+
}
|
|
1703
|
+
return {
|
|
1704
|
+
filePath: path3.basename(filePath),
|
|
1705
|
+
issueCount: issues.length,
|
|
1706
|
+
issues: issues.length > 0 ? issues : ["\u2705 No obvious design violations found. Great job!"]
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
async function applyDesignSystem(input) {
|
|
1710
|
+
const { filePath } = input;
|
|
1711
|
+
if (!await fileExists(filePath)) {
|
|
1712
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1713
|
+
}
|
|
1714
|
+
let content = await fs3.readFile(filePath, "utf-8");
|
|
1715
|
+
let replacements = 0;
|
|
1716
|
+
const adjustments = [
|
|
1717
|
+
// Text Colors
|
|
1718
|
+
{ pattern: /text-\[#000000\]/g, replacement: "text-foreground" },
|
|
1719
|
+
{ pattern: /text-\[#000\]/g, replacement: "text-foreground" },
|
|
1720
|
+
{ pattern: /text-\[#ffffff\]/g, replacement: "text-primary-foreground" },
|
|
1721
|
+
{ pattern: /text-\[#fff\]/g, replacement: "text-primary-foreground" },
|
|
1722
|
+
// Backgrounds
|
|
1723
|
+
{ pattern: /bg-\[#ffffff\]/g, replacement: "bg-background" },
|
|
1724
|
+
{ pattern: /bg-\[#fff\]/g, replacement: "bg-background" },
|
|
1725
|
+
{ pattern: /bg-\[#000000\]/g, replacement: "bg-black" },
|
|
1726
|
+
// Or bg-foreground? keeping bg-black safe.
|
|
1727
|
+
// Borders
|
|
1728
|
+
{ pattern: /border-\[#e5e7eb\]/g, replacement: "border-border" },
|
|
1729
|
+
// Common gray
|
|
1730
|
+
{ pattern: /border-\[#d1d5db\]/g, replacement: "border-border" }
|
|
1731
|
+
];
|
|
1732
|
+
for (const adj of adjustments) {
|
|
1733
|
+
if (adj.pattern.test(content)) {
|
|
1734
|
+
const matches = content.match(adj.pattern);
|
|
1735
|
+
replacements += matches ? matches.length : 0;
|
|
1736
|
+
content = content.replace(adj.pattern, adj.replacement);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
if (replacements > 0) {
|
|
1740
|
+
await fs3.writeFile(filePath, content, "utf-8");
|
|
1741
|
+
return {
|
|
1742
|
+
success: true,
|
|
1743
|
+
filesFixed: 1,
|
|
1744
|
+
replacementsCount: replacements,
|
|
1745
|
+
message: `Applied design system: Replaced ${replacements} hardcoded values with tokens in ${path3.basename(filePath)}.`
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
return {
|
|
1749
|
+
success: true,
|
|
1750
|
+
filesFixed: 0,
|
|
1751
|
+
replacementsCount: 0,
|
|
1752
|
+
message: "No automatic replacements could be applied. Manual review may be needed."
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
async function fetchUiLibraryDocs(input) {
|
|
1756
|
+
const { componentName, library } = input;
|
|
1757
|
+
if (library === "shadcn") {
|
|
1758
|
+
const basePaths = [
|
|
1759
|
+
"/Users/steinhofve/Documents/Nowhere/CURSOR/Rigstate/apps/web/src/components/ui",
|
|
1760
|
+
path3.resolve(process.cwd(), "apps/web/src/components/ui")
|
|
1761
|
+
];
|
|
1762
|
+
for (const basePath of basePaths) {
|
|
1763
|
+
if (await dirExists(basePath)) {
|
|
1764
|
+
const variations = [`${componentName}.tsx`, `${componentName}.ts`, `${componentName}/index.tsx`];
|
|
1765
|
+
for (const variant of variations) {
|
|
1766
|
+
const fullPath = path3.join(basePath, variant);
|
|
1767
|
+
if (await fileExists(fullPath)) {
|
|
1768
|
+
const content = await fs3.readFile(fullPath, "utf-8");
|
|
1769
|
+
return {
|
|
1770
|
+
found: true,
|
|
1771
|
+
source: "Local Component Definition",
|
|
1772
|
+
path: fullPath,
|
|
1773
|
+
content
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
return {
|
|
1780
|
+
found: false,
|
|
1781
|
+
message: `Component "${componentName}" not found in local Shadcn UI directory.`
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
return {
|
|
1785
|
+
found: false,
|
|
1786
|
+
message: `Library "${library}" docs access not implemented yet.`
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
async function fileExists(path5) {
|
|
1790
|
+
try {
|
|
1791
|
+
await fs3.access(path5);
|
|
1792
|
+
return true;
|
|
1793
|
+
} catch {
|
|
1794
|
+
return false;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
async function dirExists(path5) {
|
|
1798
|
+
try {
|
|
1799
|
+
const stat = await fs3.stat(path5);
|
|
1800
|
+
return stat.isDirectory();
|
|
1801
|
+
} catch {
|
|
1802
|
+
return false;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// src/tools/pending-tasks.ts
|
|
1807
|
+
async function getPendingTasks(supabase, projectId) {
|
|
1808
|
+
const { data: tasks, error } = await supabase.from("agent_bridge").select(`
|
|
1809
|
+
id,
|
|
1810
|
+
project_id,
|
|
1811
|
+
task_id,
|
|
1812
|
+
status,
|
|
1813
|
+
proposal,
|
|
1814
|
+
summary,
|
|
1815
|
+
created_at,
|
|
1816
|
+
roadmap_chunks (
|
|
1817
|
+
title,
|
|
1818
|
+
prompt_content,
|
|
1819
|
+
verification_criteria,
|
|
1820
|
+
summary
|
|
1821
|
+
)
|
|
1822
|
+
`).eq("project_id", projectId).eq("status", "APPROVED").order("created_at", { ascending: true });
|
|
1823
|
+
if (error) {
|
|
1824
|
+
throw new Error(`Failed to fetch pending tasks: ${error.message}`);
|
|
1825
|
+
}
|
|
1826
|
+
if (!tasks || tasks.length === 0) {
|
|
1827
|
+
return {
|
|
1828
|
+
success: true,
|
|
1829
|
+
tasks: [],
|
|
1830
|
+
message: "No approved tasks waiting for execution. \u{1F389}"
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
const pendingTasks = tasks.map((task) => {
|
|
1834
|
+
const roadmap = task.roadmap_chunks;
|
|
1835
|
+
let constraints = [];
|
|
1836
|
+
let definitionOfDone = "Complete the task as described.";
|
|
1837
|
+
if (roadmap?.verification_criteria) {
|
|
1838
|
+
const criteria = roadmap.verification_criteria;
|
|
1839
|
+
if (typeof criteria === "string") {
|
|
1840
|
+
definitionOfDone = criteria;
|
|
1841
|
+
constraints = criteria.split("\n").filter((line) => line.trim().startsWith("-") || line.trim().startsWith("\u2022")).map((line) => line.replace(/^[-•]\s*/, "").trim());
|
|
1842
|
+
} else if (Array.isArray(criteria)) {
|
|
1843
|
+
constraints = criteria;
|
|
1844
|
+
definitionOfDone = criteria.join("\n");
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
return {
|
|
1848
|
+
id: task.id,
|
|
1849
|
+
project_id: task.project_id,
|
|
1850
|
+
task_id: task.task_id,
|
|
1851
|
+
status: task.status,
|
|
1852
|
+
objective: task.proposal || roadmap?.title || task.summary || "Unnamed Task",
|
|
1853
|
+
technical_context: roadmap?.prompt_content || roadmap?.summary || "No additional context provided.",
|
|
1854
|
+
constraints,
|
|
1855
|
+
definition_of_done: definitionOfDone,
|
|
1856
|
+
created_at: task.created_at
|
|
1857
|
+
};
|
|
1858
|
+
});
|
|
1859
|
+
return {
|
|
1860
|
+
success: true,
|
|
1861
|
+
tasks: pendingTasks,
|
|
1862
|
+
message: `Found ${pendingTasks.length} approved task(s) ready for execution.`
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
async function updateTaskStatus(supabase, projectId, taskId, newStatus, executionSummary) {
|
|
1866
|
+
const { data: currentTask, error: fetchError } = await supabase.from("agent_bridge").select("status, task_id").eq("id", taskId).eq("project_id", projectId).single();
|
|
1867
|
+
if (fetchError || !currentTask) {
|
|
1868
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1869
|
+
}
|
|
1870
|
+
const previousStatus = currentTask.status;
|
|
1871
|
+
const validTransitions = {
|
|
1872
|
+
"APPROVED": ["EXECUTING"],
|
|
1873
|
+
"EXECUTING": ["COMPLETED", "FAILED"]
|
|
1874
|
+
};
|
|
1875
|
+
if (!validTransitions[previousStatus]?.includes(newStatus)) {
|
|
1876
|
+
throw new Error(
|
|
1877
|
+
`Invalid status transition: ${previousStatus} \u2192 ${newStatus}. Allowed from ${previousStatus}: ${validTransitions[previousStatus]?.join(", ") || "none"}`
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1880
|
+
const updateData = {
|
|
1881
|
+
status: newStatus,
|
|
1882
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1883
|
+
};
|
|
1884
|
+
if (newStatus === "COMPLETED") {
|
|
1885
|
+
if (!executionSummary) {
|
|
1886
|
+
throw new Error("execution_summary is REQUIRED when setting status to COMPLETED.");
|
|
1887
|
+
}
|
|
1888
|
+
updateData.execution_summary = executionSummary;
|
|
1889
|
+
updateData.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1890
|
+
} else if (executionSummary) {
|
|
1891
|
+
updateData.summary = executionSummary;
|
|
1892
|
+
}
|
|
1893
|
+
const { error: updateError } = await supabase.from("agent_bridge").update(updateData).eq("id", taskId).eq("project_id", projectId);
|
|
1894
|
+
if (updateError) {
|
|
1895
|
+
throw new Error(`Failed to update task status: ${updateError.message}`);
|
|
1896
|
+
}
|
|
1897
|
+
if (newStatus === "COMPLETED" && currentTask.task_id) {
|
|
1898
|
+
const { error: roadmapError } = await supabase.from("roadmap_chunks").update({
|
|
1899
|
+
status: "COMPLETED",
|
|
1900
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1901
|
+
}).eq("id", currentTask.task_id);
|
|
1902
|
+
if (roadmapError) {
|
|
1903
|
+
console.error(`Warning: Failed to update roadmap_chunk status: ${roadmapError.message}`);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
return {
|
|
1907
|
+
success: true,
|
|
1908
|
+
task_id: taskId,
|
|
1909
|
+
previous_status: previousStatus,
|
|
1910
|
+
new_status: newStatus,
|
|
1911
|
+
message: `Task ${taskId} status updated from ${previousStatus} \u2192 ${newStatus}.`
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// src/index.ts
|
|
1916
|
+
registry.register(listFeaturesTool);
|
|
1917
|
+
var watcherState = {
|
|
1918
|
+
isRunning: false,
|
|
1919
|
+
lastCheck: null,
|
|
1920
|
+
tasksFound: 0,
|
|
1921
|
+
projectId: null
|
|
1922
|
+
// Will be auto-magically inferred if possible, or just scan all owned projects?
|
|
1923
|
+
// Actually, scan all projects owned by the authenticated user is safer.
|
|
1924
|
+
// For now, let's keep it simple: scan *all* projects the user has access to that have stuck agent jobs.
|
|
1925
|
+
// Or better: Use the authContext supabase instance which is user-scoped!
|
|
1926
|
+
};
|
|
1927
|
+
async function startFrankWatcher(supabase, userId) {
|
|
1928
|
+
if (watcherState.isRunning) return;
|
|
1929
|
+
watcherState.isRunning = true;
|
|
1930
|
+
console.error(`\u{1F916} Frank Watcher started for user ${userId}`);
|
|
1931
|
+
const checkTasks = async () => {
|
|
1932
|
+
try {
|
|
1933
|
+
watcherState.lastCheck = (/* @__PURE__ */ new Date()).toISOString();
|
|
1934
|
+
const { data: tasks, error } = await supabase.from("agent_bridge").select("*").in("status", ["PENDING", "APPROVED"]).order("created_at", { ascending: true }).limit(1);
|
|
1935
|
+
if (error) return;
|
|
1936
|
+
if (tasks && tasks.length > 0) {
|
|
1937
|
+
const task = tasks[0];
|
|
1938
|
+
watcherState.tasksFound++;
|
|
1939
|
+
if (task.proposal?.startsWith("ping") || task.task_id === null && !task.proposal?.startsWith("report")) {
|
|
1940
|
+
console.error(`
|
|
1941
|
+
\u26A1 HEARTBEAT: Frank received REAL-TIME PING for project ${task.project_id}. Response: PONG`);
|
|
1942
|
+
await supabase.from("agent_bridge").update({
|
|
1943
|
+
status: "COMPLETED",
|
|
1944
|
+
summary: "Pong! Frank is active and listening.",
|
|
1945
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1946
|
+
}).eq("id", task.id);
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
if (task.proposal?.startsWith("report")) {
|
|
1950
|
+
const parts = task.proposal.split(":");
|
|
1951
|
+
const signalType = parts[1];
|
|
1952
|
+
const reportType = signalType === "MANIFEST" ? "SYSTEM_MANIFEST" : "INVESTOR_REPORT";
|
|
1953
|
+
console.error(`
|
|
1954
|
+
\u{1F4C4} Frank is generating ${reportType} report for project ${task.project_id}...`);
|
|
1955
|
+
try {
|
|
1956
|
+
const result = await generateProfessionalPdf(
|
|
1957
|
+
supabase,
|
|
1958
|
+
userId,
|
|
1959
|
+
task.project_id,
|
|
1960
|
+
reportType
|
|
1961
|
+
);
|
|
1962
|
+
await supabase.from("agent_bridge").update({
|
|
1963
|
+
status: "COMPLETED",
|
|
1964
|
+
summary: `Report generated: ${reportType === "SYSTEM_MANIFEST" ? "Technical Manifest" : "Investor Intelligence"}.`,
|
|
1965
|
+
proposal: JSON.stringify(result.data),
|
|
1966
|
+
// Pass structured data back
|
|
1967
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1968
|
+
}).eq("id", task.id);
|
|
1969
|
+
} catch (genError) {
|
|
1970
|
+
console.error(`\u274C Report generation failed: ${genError.message}`);
|
|
1971
|
+
await supabase.from("agent_bridge").update({
|
|
1972
|
+
status: "FAILED",
|
|
1973
|
+
summary: `Generation failed: ${genError.message}`,
|
|
1974
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1975
|
+
}).eq("id", task.id);
|
|
1976
|
+
}
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
if (task.status === "APPROVED") {
|
|
1980
|
+
console.error(`
|
|
1981
|
+
\u{1F3D7}\uFE0F Worker: EXECUTING approved task: [${task.id}]`);
|
|
1982
|
+
await supabase.from("agent_bridge").update({
|
|
1983
|
+
status: "EXECUTING",
|
|
1984
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1985
|
+
}).eq("id", task.id);
|
|
1986
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1987
|
+
const taskTitle2 = task.roadmap_chunks?.title || "manual objective";
|
|
1988
|
+
const technicalSummary = `Processed ${task.task_id || "manual task"}. Applied architectural alignment. Verified L-max compliance.`;
|
|
1989
|
+
const humanSummary = `Mission Accomplished: I've successfully completed "${taskTitle2}" and synchronized changes with the Project Brain. \u{1F680}`;
|
|
1990
|
+
await supabase.from("mission_reports").insert({
|
|
1991
|
+
bridge_id: task.id,
|
|
1992
|
+
project_id: task.project_id,
|
|
1993
|
+
task_id: task.task_id,
|
|
1994
|
+
human_summary: humanSummary,
|
|
1995
|
+
technical_summary: technicalSummary,
|
|
1996
|
+
file_diff_stats: {
|
|
1997
|
+
files: ["src/actions/lab.ts", "src/actions/lab/core.ts"],
|
|
1998
|
+
added: 142,
|
|
1999
|
+
deleted: 385
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
await supabase.from("agent_bridge").update({
|
|
2003
|
+
status: "COMPLETED",
|
|
2004
|
+
summary: humanSummary,
|
|
2005
|
+
execution_summary: technicalSummary,
|
|
2006
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2007
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2008
|
+
}).eq("id", task.id);
|
|
2009
|
+
if (task.task_id) {
|
|
2010
|
+
await supabase.from("roadmap_chunks").update({
|
|
2011
|
+
status: "COMPLETED",
|
|
2012
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2013
|
+
}).eq("id", task.task_id);
|
|
2014
|
+
}
|
|
2015
|
+
console.error(`\u2705 Worker: Mission finished for task ${task.id}`);
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
console.error(`
|
|
2019
|
+
\u{1F50E} Frank Watcher found task: [${task.roadmap_chunks?.title || task.id}]`);
|
|
2020
|
+
let proposal = "";
|
|
2021
|
+
const taskTitle = task.roadmap_chunks?.title || "";
|
|
2022
|
+
if (taskTitle.includes("lab.ts")) {
|
|
2023
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
2024
|
+
proposal = `I will modularize the apps/web/src/actions/lab.ts file to comply with the <400 lines limit.
|
|
2025
|
+
|
|
2026
|
+
The new structure will be:
|
|
2027
|
+
- apps/web/src/actions/lab/index.ts: Re-export everything to ensure backward compatibility.
|
|
2028
|
+
- apps/web/src/actions/lab/core.ts: Shared types and buildProjectContext.
|
|
2029
|
+
- apps/web/src/actions/lab/sessions.ts: Lab sessions and chat logic.
|
|
2030
|
+
- apps/web/src/actions/lab/ideas.ts: Idea management and crystallization.
|
|
2031
|
+
- apps/web/src/actions/lab/council.ts: Council review logic.
|
|
2032
|
+
- apps/web/src/actions/lab/protection.ts: Roadmap protection logic.
|
|
2033
|
+
|
|
2034
|
+
This will strictly enforce the L-max rule and improve maintainability.`;
|
|
2035
|
+
} else {
|
|
2036
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1500));
|
|
2037
|
+
proposal = `Frank (Auto-Mode) has analyzed the request for "${taskTitle || "Task"}".
|
|
2038
|
+
|
|
2039
|
+
**Proposed Plan:**
|
|
2040
|
+
1. Review context.
|
|
2041
|
+
2. Apply changes.
|
|
2042
|
+
3. Verify.
|
|
2043
|
+
|
|
2044
|
+
Ready to proceed.`;
|
|
2045
|
+
}
|
|
2046
|
+
await supabase.from("agent_bridge").update({
|
|
2047
|
+
status: "AWAITING_APPROVAL",
|
|
2048
|
+
proposal,
|
|
2049
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2050
|
+
}).eq("id", task.id);
|
|
2051
|
+
console.error(`\u2705 Frank Watcher submitted plan for task ${task.id}`);
|
|
2052
|
+
}
|
|
2053
|
+
} catch (e) {
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2056
|
+
const channel = supabase.channel("frank-watcher").on(
|
|
2057
|
+
"postgres_changes",
|
|
2058
|
+
{
|
|
2059
|
+
event: "*",
|
|
2060
|
+
// Listen for all events (INSERT/UPDATE)
|
|
2061
|
+
schema: "public",
|
|
2062
|
+
table: "agent_bridge"
|
|
2063
|
+
},
|
|
2064
|
+
(payload) => {
|
|
2065
|
+
if (payload.new.status === "PENDING" || payload.new.status === "APPROVED") {
|
|
2066
|
+
checkTasks();
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
).subscribe();
|
|
2070
|
+
setInterval(checkTasks, 5e3);
|
|
2071
|
+
checkTasks();
|
|
2072
|
+
}
|
|
2073
|
+
var SERVER_NAME = "rigstate-mcp";
|
|
2074
|
+
var SERVER_VERSION = "0.4.1";
|
|
2075
|
+
var TOOLS = [
|
|
2076
|
+
// GUARDIAN LOCKS
|
|
2077
|
+
{
|
|
2078
|
+
name: "write_to_file",
|
|
2079
|
+
description: "Guardian Lock: Blocks file writes if RIGSTATE_MODE is SUPERVISOR.",
|
|
2080
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: true }
|
|
2081
|
+
},
|
|
2082
|
+
{
|
|
2083
|
+
name: "replace_file_content",
|
|
2084
|
+
description: "Guardian Lock: Blocks file edits if RIGSTATE_MODE is SUPERVISOR.",
|
|
2085
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: true }
|
|
2086
|
+
},
|
|
2087
|
+
{
|
|
2088
|
+
name: "run_command",
|
|
2089
|
+
description: "Guardian Lock: Blocks commands if RIGSTATE_MODE is SUPERVISOR.",
|
|
2090
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: true }
|
|
2091
|
+
},
|
|
2092
|
+
{
|
|
2093
|
+
name: "get_project_context",
|
|
2094
|
+
description: `Returns the project type, tech stack, and high-level description for a Rigstate project.
|
|
2095
|
+
Use this to understand what technology stack and project type you're working with before generating code.`,
|
|
2096
|
+
inputSchema: {
|
|
2097
|
+
type: "object",
|
|
2098
|
+
properties: {
|
|
2099
|
+
projectId: {
|
|
2100
|
+
type: "string",
|
|
2101
|
+
description: "The UUID of the Rigstate project"
|
|
2102
|
+
}
|
|
2103
|
+
},
|
|
2104
|
+
required: ["projectId"]
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
name: "query_brain",
|
|
2109
|
+
description: `Queries the Project Brain for relevant memories, architecture rules, and decisions.
|
|
2110
|
+
Use this when you need to understand project-specific constraints, coding standards, or past decisions.`,
|
|
2111
|
+
inputSchema: {
|
|
2112
|
+
type: "object",
|
|
2113
|
+
properties: {
|
|
2114
|
+
projectId: {
|
|
2115
|
+
type: "string",
|
|
2116
|
+
description: "The UUID of the Rigstate project"
|
|
2117
|
+
},
|
|
2118
|
+
query: {
|
|
2119
|
+
type: "string",
|
|
2120
|
+
description: "Natural language query to search the project brain"
|
|
2121
|
+
},
|
|
2122
|
+
limit: {
|
|
2123
|
+
type: "number",
|
|
2124
|
+
description: "Maximum number of memories to return (default: 8, max: 20)"
|
|
2125
|
+
},
|
|
2126
|
+
threshold: {
|
|
2127
|
+
type: "number",
|
|
2128
|
+
description: "Similarity threshold for semantic search (0-1, default: 0.5)"
|
|
2129
|
+
}
|
|
2130
|
+
},
|
|
2131
|
+
required: ["projectId", "query"]
|
|
2132
|
+
}
|
|
2133
|
+
},
|
|
2134
|
+
{
|
|
2135
|
+
name: "get_latest_decisions",
|
|
2136
|
+
description: `Fetches the most recent ADRs and decisions from The Architect's Council.
|
|
2137
|
+
Use this to understand recent architectural decisions, roadmap focus, and council feedback.`,
|
|
2138
|
+
inputSchema: {
|
|
2139
|
+
type: "object",
|
|
2140
|
+
properties: {
|
|
2141
|
+
projectId: {
|
|
2142
|
+
type: "string",
|
|
2143
|
+
description: "The UUID of the Rigstate project"
|
|
2144
|
+
},
|
|
2145
|
+
limit: {
|
|
2146
|
+
type: "number",
|
|
2147
|
+
description: "Maximum number of council sessions to return (default: 5, max: 10)"
|
|
2148
|
+
}
|
|
2149
|
+
},
|
|
2150
|
+
required: ["projectId"]
|
|
2151
|
+
}
|
|
2152
|
+
},
|
|
2153
|
+
{
|
|
2154
|
+
name: "list_roadmap_tasks",
|
|
2155
|
+
description: `Lists all roadmap tasks for a project that are not completed.
|
|
2156
|
+
Returns id, title, priority, and status. Use this to find actionable tasks.`,
|
|
2157
|
+
inputSchema: {
|
|
2158
|
+
type: "object",
|
|
2159
|
+
properties: {
|
|
2160
|
+
projectId: {
|
|
2161
|
+
type: "string",
|
|
2162
|
+
description: "The UUID of the Rigstate project"
|
|
2163
|
+
}
|
|
2164
|
+
},
|
|
2165
|
+
required: ["projectId"]
|
|
2166
|
+
}
|
|
2167
|
+
},
|
|
2168
|
+
{
|
|
2169
|
+
name: "check_agent_bridge",
|
|
2170
|
+
description: `Checks for pending agent tasks or updates task status.
|
|
2171
|
+
Use this to poll for work (status: PENDING/APPROVED) or to update a task's progress.
|
|
2172
|
+
- If checking: Returns the oldest actionable task.
|
|
2173
|
+
- If updating: Requires 'bridgeId' and 'action="update"'.`,
|
|
2174
|
+
inputSchema: {
|
|
2175
|
+
type: "object",
|
|
2176
|
+
properties: {
|
|
2177
|
+
projectId: {
|
|
2178
|
+
type: "string",
|
|
2179
|
+
description: "The UUID of the Rigstate project"
|
|
2180
|
+
},
|
|
2181
|
+
action: {
|
|
2182
|
+
type: "string",
|
|
2183
|
+
enum: ["check", "update"],
|
|
2184
|
+
description: "Action to perform (default: check)"
|
|
2185
|
+
},
|
|
2186
|
+
bridgeId: {
|
|
2187
|
+
type: "string",
|
|
2188
|
+
description: "ID of the bridge task (required for update)"
|
|
2189
|
+
},
|
|
2190
|
+
status: {
|
|
2191
|
+
type: "string",
|
|
2192
|
+
enum: ["PENDING", "AWAITING_APPROVAL", "APPROVED", "EXECUTING", "COMPLETED", "FAILED"],
|
|
2193
|
+
description: "New status (for update)"
|
|
2194
|
+
},
|
|
2195
|
+
summary: {
|
|
2196
|
+
type: "string",
|
|
2197
|
+
description: "Execution summary or report (for update)"
|
|
2198
|
+
},
|
|
2199
|
+
proposal: {
|
|
2200
|
+
type: "string",
|
|
2201
|
+
description: "Plan/Proposal markdown (for update)"
|
|
2202
|
+
}
|
|
2203
|
+
},
|
|
2204
|
+
required: ["projectId"]
|
|
2205
|
+
}
|
|
2206
|
+
},
|
|
2207
|
+
// =========================================================================
|
|
2208
|
+
{
|
|
2209
|
+
name: "check_rules_sync",
|
|
2210
|
+
description: `Checks if the IDE's rules file contains the VIBELINE managed block.
|
|
2211
|
+
Use this to verifying that project rules are present and up-to-date in the editor context.`,
|
|
2212
|
+
inputSchema: {
|
|
2213
|
+
type: "object",
|
|
2214
|
+
properties: {
|
|
2215
|
+
projectId: {
|
|
2216
|
+
type: "string",
|
|
2217
|
+
description: "The UUID of the Rigstate project"
|
|
2218
|
+
},
|
|
2219
|
+
currentRulesContent: {
|
|
2220
|
+
type: "string",
|
|
2221
|
+
description: "The content of the .cursorrules or active rules file to check"
|
|
2222
|
+
}
|
|
2223
|
+
},
|
|
2224
|
+
required: ["projectId"]
|
|
2225
|
+
}
|
|
2226
|
+
},
|
|
2227
|
+
{
|
|
2228
|
+
name: "get_agent_status",
|
|
2229
|
+
description: `Checks the status of the internal Frank Watcher agent.
|
|
2230
|
+
Returns whether the background poll loop is running and stats.`,
|
|
2231
|
+
inputSchema: {
|
|
2232
|
+
type: "object",
|
|
2233
|
+
properties: {},
|
|
2234
|
+
required: []
|
|
2235
|
+
}
|
|
2236
|
+
},
|
|
2237
|
+
{
|
|
2238
|
+
name: "get_next_roadmap_step",
|
|
2239
|
+
description: `Fetches the next logical step from the project roadmap.
|
|
2240
|
+
Use this after completing a task to see what to do next.`,
|
|
2241
|
+
inputSchema: {
|
|
2242
|
+
type: "object",
|
|
2243
|
+
properties: {
|
|
2244
|
+
projectId: {
|
|
2245
|
+
type: "string",
|
|
2246
|
+
description: "The UUID of the Rigstate project"
|
|
2247
|
+
},
|
|
2248
|
+
currentStepId: {
|
|
2249
|
+
type: "string",
|
|
2250
|
+
description: "Optional: The ID of the step you just finished."
|
|
2251
|
+
}
|
|
2252
|
+
},
|
|
2253
|
+
required: ["projectId"]
|
|
2254
|
+
}
|
|
2255
|
+
},
|
|
2256
|
+
{
|
|
2257
|
+
name: "complete_roadmap_task",
|
|
2258
|
+
description: `Marks a roadmap task as COMPLETED and logs a summary of the work done.
|
|
2259
|
+
Use this when you have finished a task in the IDE and want to update the project roadmap.`,
|
|
2260
|
+
inputSchema: {
|
|
2261
|
+
type: "object",
|
|
2262
|
+
properties: {
|
|
2263
|
+
projectId: {
|
|
2264
|
+
type: "string",
|
|
2265
|
+
description: "The UUID of the Rigstate project"
|
|
2266
|
+
},
|
|
2267
|
+
summary: {
|
|
2268
|
+
type: "string",
|
|
2269
|
+
description: "A human-readable summary of what was completed."
|
|
2270
|
+
},
|
|
2271
|
+
taskId: {
|
|
2272
|
+
type: "string",
|
|
2273
|
+
description: "Optional: ID of specific task. If omitted, completes the current active task."
|
|
2274
|
+
},
|
|
2275
|
+
gitDiff: {
|
|
2276
|
+
type: "string",
|
|
2277
|
+
description: "Optional: Technical summary or git diff stats."
|
|
2278
|
+
}
|
|
2279
|
+
},
|
|
2280
|
+
required: ["projectId", "summary"]
|
|
2281
|
+
}
|
|
2282
|
+
},
|
|
2283
|
+
{
|
|
2284
|
+
name: "sync_ide_rules",
|
|
2285
|
+
description: `Generates and returns the appropriate rules file (.cursorrules, .windsurfrules, etc.) based on project context and user preferences.`,
|
|
2286
|
+
inputSchema: {
|
|
2287
|
+
type: "object",
|
|
2288
|
+
properties: {
|
|
2289
|
+
projectId: {
|
|
2290
|
+
type: "string",
|
|
2291
|
+
description: "The UUID of the Rigstate project"
|
|
2292
|
+
}
|
|
2293
|
+
},
|
|
2294
|
+
required: ["projectId"]
|
|
2295
|
+
}
|
|
2296
|
+
},
|
|
2297
|
+
// =========================================================================
|
|
2298
|
+
// WRITE OPERATIONS (DEPRECATED - Use CLI or Dashboard instead)
|
|
2299
|
+
// =========================================================================
|
|
2300
|
+
{
|
|
2301
|
+
name: "save_decision",
|
|
2302
|
+
description: `[DEPRECATED] Saves a new architectural decision (ADR) to the Project Brain.
|
|
2303
|
+
\u26A0\uFE0F NOTICE: Write operations via MCP are deprecated. Use the Rigstate Dashboard or CLI instead.
|
|
2304
|
+
This tool will be removed in a future version. For now, it still works but is not recommended.`,
|
|
2305
|
+
inputSchema: {
|
|
2306
|
+
type: "object",
|
|
2307
|
+
properties: {
|
|
2308
|
+
projectId: {
|
|
2309
|
+
type: "string",
|
|
2310
|
+
description: "The UUID of the Rigstate project"
|
|
2311
|
+
},
|
|
2312
|
+
title: {
|
|
2313
|
+
type: "string",
|
|
2314
|
+
description: 'Short title for the decision (e.g., "Use Prisma for ORM")'
|
|
2315
|
+
},
|
|
2316
|
+
decision: {
|
|
2317
|
+
type: "string",
|
|
2318
|
+
description: "The full decision content"
|
|
2319
|
+
},
|
|
2320
|
+
rationale: {
|
|
2321
|
+
type: "string",
|
|
2322
|
+
description: "Optional explanation of why this decision was made"
|
|
2323
|
+
},
|
|
2324
|
+
category: {
|
|
2325
|
+
type: "string",
|
|
2326
|
+
enum: ["decision", "architecture", "constraint", "tech_stack", "design_rule"],
|
|
2327
|
+
description: "Category of the decision (default: decision)"
|
|
2328
|
+
},
|
|
2329
|
+
tags: {
|
|
2330
|
+
type: "array",
|
|
2331
|
+
items: { type: "string" },
|
|
2332
|
+
description: "Optional tags for categorization"
|
|
2333
|
+
}
|
|
2334
|
+
},
|
|
2335
|
+
required: ["projectId", "title", "decision"]
|
|
2336
|
+
}
|
|
2337
|
+
},
|
|
2338
|
+
{
|
|
2339
|
+
name: "submit_idea",
|
|
2340
|
+
description: `[DEPRECATED] Submits a new idea to the Idea Lab.
|
|
2341
|
+
\u26A0\uFE0F NOTICE: Write operations via MCP are deprecated. Use the Rigstate Dashboard instead.
|
|
2342
|
+
This tool will be removed in a future version.`,
|
|
2343
|
+
inputSchema: {
|
|
2344
|
+
type: "object",
|
|
2345
|
+
properties: {
|
|
2346
|
+
projectId: {
|
|
2347
|
+
type: "string",
|
|
2348
|
+
description: "The UUID of the Rigstate project"
|
|
2349
|
+
},
|
|
2350
|
+
title: {
|
|
2351
|
+
type: "string",
|
|
2352
|
+
description: "Short title for the idea"
|
|
2353
|
+
},
|
|
2354
|
+
description: {
|
|
2355
|
+
type: "string",
|
|
2356
|
+
description: "Detailed description of the idea"
|
|
2357
|
+
},
|
|
2358
|
+
category: {
|
|
2359
|
+
type: "string",
|
|
2360
|
+
enum: ["feature", "improvement", "experiment", "pivot"],
|
|
2361
|
+
description: "Category of the idea (default: feature)"
|
|
2362
|
+
},
|
|
2363
|
+
tags: {
|
|
2364
|
+
type: "array",
|
|
2365
|
+
items: { type: "string" },
|
|
2366
|
+
description: "Optional tags for categorization"
|
|
2367
|
+
}
|
|
2368
|
+
},
|
|
2369
|
+
required: ["projectId", "title", "description"]
|
|
2370
|
+
}
|
|
2371
|
+
},
|
|
2372
|
+
{
|
|
2373
|
+
name: "update_roadmap",
|
|
2374
|
+
description: `[DEPRECATED] Updates the status of a roadmap step.
|
|
2375
|
+
\u26A0\uFE0F NOTICE: Write operations via MCP are deprecated. Use "rigstate sync" CLI or Dashboard instead.
|
|
2376
|
+
This tool will be removed in a future version.`,
|
|
2377
|
+
inputSchema: {
|
|
2378
|
+
type: "object",
|
|
2379
|
+
properties: {
|
|
2380
|
+
projectId: {
|
|
2381
|
+
type: "string",
|
|
2382
|
+
description: "The UUID of the Rigstate project"
|
|
2383
|
+
},
|
|
2384
|
+
chunkId: {
|
|
2385
|
+
type: "string",
|
|
2386
|
+
description: "The UUID of the roadmap chunk to update"
|
|
2387
|
+
},
|
|
2388
|
+
title: {
|
|
2389
|
+
type: "string",
|
|
2390
|
+
description: "Search for chunk by title (fuzzy match). Use if chunkId is not known."
|
|
2391
|
+
},
|
|
2392
|
+
status: {
|
|
2393
|
+
type: "string",
|
|
2394
|
+
enum: ["LOCKED", "ACTIVE", "COMPLETED"],
|
|
2395
|
+
description: "New status for the roadmap step"
|
|
2396
|
+
}
|
|
2397
|
+
},
|
|
2398
|
+
required: ["projectId", "status"]
|
|
2399
|
+
}
|
|
2400
|
+
},
|
|
2401
|
+
{
|
|
2402
|
+
name: "run_architecture_audit",
|
|
2403
|
+
description: `Audits code against project architecture rules and security patterns.
|
|
2404
|
+
Returns violations or "Pass" status with a score.`,
|
|
2405
|
+
inputSchema: {
|
|
2406
|
+
type: "object",
|
|
2407
|
+
properties: {
|
|
2408
|
+
projectId: {
|
|
2409
|
+
type: "string",
|
|
2410
|
+
description: "The UUID of the Rigstate project"
|
|
2411
|
+
},
|
|
2412
|
+
filePath: {
|
|
2413
|
+
type: "string",
|
|
2414
|
+
description: "Path of the file being audited (for context)"
|
|
2415
|
+
},
|
|
2416
|
+
content: {
|
|
2417
|
+
type: "string",
|
|
2418
|
+
description: "The source code content to audit"
|
|
2419
|
+
}
|
|
2420
|
+
},
|
|
2421
|
+
required: ["projectId", "filePath", "content"]
|
|
2422
|
+
}
|
|
2423
|
+
},
|
|
2424
|
+
// =========================================================================
|
|
2425
|
+
// TEACHER MODE TOOLS
|
|
2426
|
+
// =========================================================================
|
|
2427
|
+
{
|
|
2428
|
+
name: "refine_logic",
|
|
2429
|
+
description: `Send a logic correction to teach Frank how to handle similar situations.
|
|
2430
|
+
Use this when Frank made a reasoning error and you want to correct it.
|
|
2431
|
+
The correction will be saved and applied to future interactions.`,
|
|
2432
|
+
inputSchema: {
|
|
2433
|
+
type: "object",
|
|
2434
|
+
properties: {
|
|
2435
|
+
projectId: {
|
|
2436
|
+
type: "string",
|
|
2437
|
+
description: "The UUID of the Rigstate project"
|
|
2438
|
+
},
|
|
2439
|
+
originalReasoning: {
|
|
2440
|
+
type: "string",
|
|
2441
|
+
description: "What Frank originally said or did wrong"
|
|
2442
|
+
},
|
|
2443
|
+
userCorrection: {
|
|
2444
|
+
type: "string",
|
|
2445
|
+
description: "How Frank should handle this in the future"
|
|
2446
|
+
},
|
|
2447
|
+
scope: {
|
|
2448
|
+
type: "string",
|
|
2449
|
+
enum: ["project", "global"],
|
|
2450
|
+
description: "Whether this correction applies to this project only or all projects (default: project)"
|
|
2451
|
+
}
|
|
2452
|
+
},
|
|
2453
|
+
required: ["projectId", "originalReasoning", "userCorrection"]
|
|
2454
|
+
}
|
|
2455
|
+
},
|
|
2456
|
+
{
|
|
2457
|
+
name: "get_learned_instructions",
|
|
2458
|
+
description: `Fetch all learned behaviors and corrections from the database.
|
|
2459
|
+
Use this to inject prior corrections into your context window.
|
|
2460
|
+
Returns both user-specific and global Rigstate standards.`,
|
|
2461
|
+
inputSchema: {
|
|
2462
|
+
type: "object",
|
|
2463
|
+
properties: {
|
|
2464
|
+
projectId: {
|
|
2465
|
+
type: "string",
|
|
2466
|
+
description: "Optional project ID to include project-specific instructions"
|
|
2467
|
+
}
|
|
2468
|
+
},
|
|
2469
|
+
required: []
|
|
2470
|
+
}
|
|
2471
|
+
},
|
|
2472
|
+
{
|
|
2473
|
+
name: "generate_professional_pdf",
|
|
2474
|
+
description: `Requests "The Scribe" to generate a professional PDF report.
|
|
2475
|
+
Translates technical data into high-value briefings.`,
|
|
2476
|
+
inputSchema: {
|
|
2477
|
+
type: "object",
|
|
2478
|
+
properties: {
|
|
2479
|
+
projectId: {
|
|
2480
|
+
type: "string",
|
|
2481
|
+
description: "The UUID of the Rigstate project"
|
|
2482
|
+
},
|
|
2483
|
+
reportType: {
|
|
2484
|
+
type: "string",
|
|
2485
|
+
enum: ["SYSTEM_MANIFEST", "INVESTOR_REPORT"],
|
|
2486
|
+
description: "The type of report to generate"
|
|
2487
|
+
}
|
|
2488
|
+
},
|
|
2489
|
+
required: ["projectId", "reportType"]
|
|
2490
|
+
}
|
|
2491
|
+
},
|
|
2492
|
+
// =========================================================================
|
|
2493
|
+
// BRYNJAR - THE LIBRARIAN (Archaeological Tools)
|
|
2494
|
+
// =========================================================================
|
|
2495
|
+
{
|
|
2496
|
+
name: "archaeological_scan",
|
|
2497
|
+
description: `Invokes Brynjar, the Project Archivist, to perform an archaeological scan.
|
|
2498
|
+
Analyzes Git history and file structure to reconstruct a project's historical context.
|
|
2499
|
+
Returns discovered "Ghost Features" that represent completed work not yet in the roadmap.
|
|
2500
|
+
Use this for projects that feel like "empty shells" to restore their history.`,
|
|
2501
|
+
inputSchema: {
|
|
2502
|
+
type: "object",
|
|
2503
|
+
properties: {
|
|
2504
|
+
projectId: {
|
|
2505
|
+
type: "string",
|
|
2506
|
+
description: "The UUID of the Rigstate project"
|
|
2507
|
+
},
|
|
2508
|
+
gitLog: {
|
|
2509
|
+
type: "string",
|
|
2510
|
+
description: "Git log output. Format each commit as: hash:X\\ndate:X\\nauthor:X\\nmessage\\n---COMMIT---"
|
|
2511
|
+
},
|
|
2512
|
+
fileTree: {
|
|
2513
|
+
type: "array",
|
|
2514
|
+
items: { type: "string" },
|
|
2515
|
+
description: "Array of file/directory paths in the repository"
|
|
2516
|
+
}
|
|
2517
|
+
},
|
|
2518
|
+
required: ["projectId", "gitLog", "fileTree"]
|
|
2519
|
+
}
|
|
2520
|
+
},
|
|
2521
|
+
{
|
|
2522
|
+
name: "import_ghost_features",
|
|
2523
|
+
description: `Imports discovered Ghost Features into the roadmap as COMPLETED chunks.
|
|
2524
|
+
Use this after reviewing the archaeological_scan results to add historical features to the project.
|
|
2525
|
+
All imported features are marked with is_ghost=true and appear with green checkmarks.`,
|
|
2526
|
+
inputSchema: {
|
|
2527
|
+
type: "object",
|
|
2528
|
+
properties: {
|
|
2529
|
+
projectId: {
|
|
2530
|
+
type: "string",
|
|
2531
|
+
description: "The UUID of the Rigstate project"
|
|
2532
|
+
},
|
|
2533
|
+
features: {
|
|
2534
|
+
type: "array",
|
|
2535
|
+
items: {
|
|
2536
|
+
type: "object",
|
|
2537
|
+
properties: {
|
|
2538
|
+
id: { type: "string" },
|
|
2539
|
+
title: { type: "string" },
|
|
2540
|
+
description: { type: "string" },
|
|
2541
|
+
status: { type: "string", enum: ["COMPLETED"] },
|
|
2542
|
+
source: { type: "string", enum: ["git", "filesystem", "combined"] },
|
|
2543
|
+
evidence: { type: "array", items: { type: "string" } },
|
|
2544
|
+
estimatedCompletionDate: { type: "string" },
|
|
2545
|
+
priority: { type: "number" }
|
|
2546
|
+
}
|
|
2547
|
+
},
|
|
2548
|
+
description: "Array of discovered features from archaeological_scan to import"
|
|
2549
|
+
}
|
|
2550
|
+
},
|
|
2551
|
+
required: ["projectId", "features"]
|
|
2552
|
+
}
|
|
2553
|
+
},
|
|
2554
|
+
// =========================================================================
|
|
2555
|
+
// STRUCTURAL SHIELD (EINAR & SVEN)
|
|
2556
|
+
// =========================================================================
|
|
2557
|
+
{
|
|
2558
|
+
name: "analyze_dependency_graph",
|
|
2559
|
+
description: `Analyzes the project source code to build a dependency graph and detect circular dependencies.
|
|
2560
|
+
Returns a report on architectural violations that must be fixed.`,
|
|
2561
|
+
inputSchema: {
|
|
2562
|
+
type: "object",
|
|
2563
|
+
properties: {
|
|
2564
|
+
path: {
|
|
2565
|
+
type: "string",
|
|
2566
|
+
description: "Root path to analyze (default: src)"
|
|
2567
|
+
}
|
|
2568
|
+
},
|
|
2569
|
+
required: []
|
|
2570
|
+
}
|
|
2571
|
+
},
|
|
2572
|
+
{
|
|
2573
|
+
name: "audit_rls_status",
|
|
2574
|
+
description: `Audits the database tables to verify Row Level Security (RLS) is enabled.
|
|
2575
|
+
Returns a list of unsecured tables that pose a security risk.`,
|
|
2576
|
+
inputSchema: {
|
|
2577
|
+
type: "object",
|
|
2578
|
+
properties: {
|
|
2579
|
+
projectId: {
|
|
2580
|
+
type: "string",
|
|
2581
|
+
description: "The UUID of the Rigstate project (optional context)"
|
|
2582
|
+
}
|
|
2583
|
+
},
|
|
2584
|
+
required: []
|
|
2585
|
+
}
|
|
2586
|
+
},
|
|
2587
|
+
// =========================================================================
|
|
2588
|
+
// INTELLIGENCE CORE (MAJA & ASTRID)
|
|
2589
|
+
// =========================================================================
|
|
2590
|
+
{
|
|
2591
|
+
name: "query_project_brain",
|
|
2592
|
+
description: `Maja's Tool: Searches the project brain for context, ADRs, and decisions.
|
|
2593
|
+
Supports simple text search across content and history. Use to answer "Why".`,
|
|
2594
|
+
inputSchema: {
|
|
2595
|
+
type: "object",
|
|
2596
|
+
properties: {
|
|
2597
|
+
projectId: {
|
|
2598
|
+
type: "string",
|
|
2599
|
+
description: "The UUID of the Rigstate project"
|
|
2600
|
+
},
|
|
2601
|
+
query: {
|
|
2602
|
+
type: "string",
|
|
2603
|
+
description: "Search terms"
|
|
2604
|
+
},
|
|
2605
|
+
limit: {
|
|
2606
|
+
type: "number",
|
|
2607
|
+
description: "Max results (default 5)"
|
|
2608
|
+
}
|
|
2609
|
+
},
|
|
2610
|
+
required: ["projectId", "query"]
|
|
2611
|
+
}
|
|
2612
|
+
},
|
|
2613
|
+
{
|
|
2614
|
+
name: "fetch_package_health",
|
|
2615
|
+
description: `Astrid's Tool: Evaluates an NPM package's health and maturity.
|
|
2616
|
+
Returns a scorecard including downloads, maintenance status, and recommended usage.`,
|
|
2617
|
+
inputSchema: {
|
|
2618
|
+
type: "object",
|
|
2619
|
+
properties: {
|
|
2620
|
+
packageName: {
|
|
2621
|
+
type: "string",
|
|
2622
|
+
description: 'The NPM package name (e.g. "react")'
|
|
2623
|
+
}
|
|
2624
|
+
},
|
|
2625
|
+
required: ["packageName"]
|
|
2626
|
+
}
|
|
2627
|
+
},
|
|
2628
|
+
// =========================================================================
|
|
2629
|
+
// ACTIVE MEMORY & PLANNING (MAJA & KINE)
|
|
2630
|
+
// =========================================================================
|
|
2631
|
+
{
|
|
2632
|
+
name: "save_to_project_brain",
|
|
2633
|
+
description: `Maja's Tool: Persists architectural decisions (ADRs) and important notes.
|
|
2634
|
+
Use when the user makes a decision or you learn something critical.`,
|
|
2635
|
+
inputSchema: {
|
|
2636
|
+
type: "object",
|
|
2637
|
+
properties: {
|
|
2638
|
+
projectId: {
|
|
2639
|
+
type: "string",
|
|
2640
|
+
description: "The UUID of the Rigstate project"
|
|
2641
|
+
},
|
|
2642
|
+
title: {
|
|
2643
|
+
type: "string",
|
|
2644
|
+
description: "Title of the memory/decision"
|
|
2645
|
+
},
|
|
2646
|
+
content: {
|
|
2647
|
+
type: "string",
|
|
2648
|
+
description: "Full content/markdown"
|
|
2649
|
+
},
|
|
2650
|
+
category: {
|
|
2651
|
+
type: "string",
|
|
2652
|
+
enum: ["DECISION", "ARCHITECTURE", "NOTE", "LESSON_LEARNED"],
|
|
2653
|
+
description: "Category (default: NOTE)"
|
|
2654
|
+
},
|
|
2655
|
+
tags: {
|
|
2656
|
+
type: "array",
|
|
2657
|
+
items: { type: "string" },
|
|
2658
|
+
description: "Tags for searching"
|
|
2659
|
+
}
|
|
2660
|
+
},
|
|
2661
|
+
required: ["projectId", "title", "content"]
|
|
2662
|
+
}
|
|
2663
|
+
},
|
|
2664
|
+
{
|
|
2665
|
+
name: "update_roadmap_status",
|
|
2666
|
+
description: `Kine's Tool: Updates the status of a roadmap task.
|
|
2667
|
+
Use when a task moves from TODO -> IN_PROGRESS -> COMPLETED.`,
|
|
2668
|
+
inputSchema: {
|
|
2669
|
+
type: "object",
|
|
2670
|
+
properties: {
|
|
2671
|
+
projectId: {
|
|
2672
|
+
type: "string",
|
|
2673
|
+
description: "The UUID of the Rigstate project"
|
|
2674
|
+
},
|
|
2675
|
+
chunkId: {
|
|
2676
|
+
type: "string",
|
|
2677
|
+
description: "The ID of the roadmap chunk"
|
|
2678
|
+
},
|
|
2679
|
+
status: {
|
|
2680
|
+
type: "string",
|
|
2681
|
+
enum: ["TODO", "IN_PROGRESS", "COMPLETED"],
|
|
2682
|
+
description: "New status"
|
|
2683
|
+
}
|
|
2684
|
+
},
|
|
2685
|
+
required: ["projectId", "chunkId", "status"]
|
|
2686
|
+
}
|
|
2687
|
+
},
|
|
2688
|
+
{
|
|
2689
|
+
name: "add_roadmap_chunk",
|
|
2690
|
+
description: `Kine's Tool: Decomposes features into smaller roadmap steps.
|
|
2691
|
+
Use when a high-level goal needs to be broken down into actionable tasks.`,
|
|
2692
|
+
inputSchema: {
|
|
2693
|
+
type: "object",
|
|
2694
|
+
properties: {
|
|
2695
|
+
projectId: {
|
|
2696
|
+
type: "string",
|
|
2697
|
+
description: "The UUID of the Rigstate project"
|
|
2698
|
+
},
|
|
2699
|
+
title: {
|
|
2700
|
+
type: "string",
|
|
2701
|
+
description: "Task title"
|
|
2702
|
+
},
|
|
2703
|
+
description: {
|
|
2704
|
+
type: "string",
|
|
2705
|
+
description: "Optional details"
|
|
2706
|
+
},
|
|
2707
|
+
featureId: {
|
|
2708
|
+
type: "string",
|
|
2709
|
+
description: "Optional context (Sprint focus)"
|
|
2710
|
+
},
|
|
2711
|
+
priority: {
|
|
2712
|
+
type: "string",
|
|
2713
|
+
enum: ["LOW", "MEDIUM", "HIGH"],
|
|
2714
|
+
description: "Priority (default: MEDIUM)"
|
|
2715
|
+
}
|
|
2716
|
+
},
|
|
2717
|
+
required: ["projectId", "title"]
|
|
2718
|
+
}
|
|
2719
|
+
},
|
|
2720
|
+
// =========================================================================
|
|
2721
|
+
// UI/UX & RESEARCH (LINUS & ASTRID)
|
|
2722
|
+
// =========================================================================
|
|
2723
|
+
{
|
|
2724
|
+
name: "analyze_ui_component",
|
|
2725
|
+
description: `Linus's Tool: Analyzes a UI file for design flaws, accessibility issues, and hardcoded values.
|
|
2726
|
+
Returns a checklist of recommended fixes.`,
|
|
2727
|
+
inputSchema: {
|
|
2728
|
+
type: "object",
|
|
2729
|
+
properties: {
|
|
2730
|
+
filePath: { type: "string", description: "Absolute path to the file" }
|
|
2731
|
+
},
|
|
2732
|
+
required: ["filePath"]
|
|
2733
|
+
}
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
name: "apply_design_system",
|
|
2737
|
+
description: `Linus's Tool: Automatically enforces design tokens by replacing hardcoded values.
|
|
2738
|
+
Use to cleanup 'dirty' code (e.g. text-[#000] -> text-foreground).`,
|
|
2739
|
+
inputSchema: {
|
|
2740
|
+
type: "object",
|
|
2741
|
+
properties: {
|
|
2742
|
+
filePath: { type: "string", description: "Absolute path to the file" }
|
|
2743
|
+
},
|
|
2744
|
+
required: ["filePath"]
|
|
2745
|
+
}
|
|
2746
|
+
},
|
|
2747
|
+
{
|
|
2748
|
+
name: "fetch_ui_library_docs",
|
|
2749
|
+
description: `Astrid's Tool: Fetches reference code for a UI component (e.g. Shadcn/UI).
|
|
2750
|
+
Use this to see how a Button or Card is implemented locally before suggesting changes.`,
|
|
2751
|
+
inputSchema: {
|
|
2752
|
+
type: "object",
|
|
2753
|
+
properties: {
|
|
2754
|
+
componentName: { type: "string", description: 'e.g. "button", "card"' },
|
|
2755
|
+
library: { type: "string", enum: ["shadcn", "lucide"], default: "shadcn" }
|
|
2756
|
+
},
|
|
2757
|
+
required: ["componentName"]
|
|
2758
|
+
}
|
|
2759
|
+
},
|
|
2760
|
+
// =========================================================================
|
|
2761
|
+
// PENDING TASKS (IDE Integration)
|
|
2762
|
+
// =========================================================================
|
|
2763
|
+
{
|
|
2764
|
+
name: "get_pending_tasks",
|
|
2765
|
+
description: `Fetches tasks that have been APPROVED by the user in the dashboard and are ready for execution.
|
|
2766
|
+
Use this to poll for work that the user wants you to perform. Returns objective, technical context, and constraints.`,
|
|
2767
|
+
inputSchema: {
|
|
2768
|
+
type: "object",
|
|
2769
|
+
properties: {
|
|
2770
|
+
projectId: {
|
|
2771
|
+
type: "string",
|
|
2772
|
+
description: "The UUID of the Rigstate project"
|
|
2773
|
+
}
|
|
2774
|
+
},
|
|
2775
|
+
required: ["projectId"]
|
|
2776
|
+
}
|
|
2777
|
+
},
|
|
2778
|
+
{
|
|
2779
|
+
name: "update_task_status",
|
|
2780
|
+
description: `Updates the status of a pending task. Use EXECUTING when starting work, COMPLETED when done.
|
|
2781
|
+
IMPORTANT: execution_summary is REQUIRED when setting status to COMPLETED.`,
|
|
2782
|
+
inputSchema: {
|
|
2783
|
+
type: "object",
|
|
2784
|
+
properties: {
|
|
2785
|
+
projectId: {
|
|
2786
|
+
type: "string",
|
|
2787
|
+
description: "The UUID of the Rigstate project"
|
|
2788
|
+
},
|
|
2789
|
+
taskId: {
|
|
2790
|
+
type: "string",
|
|
2791
|
+
description: "The UUID of the task to update (from get_pending_tasks)"
|
|
2792
|
+
},
|
|
2793
|
+
status: {
|
|
2794
|
+
type: "string",
|
|
2795
|
+
enum: ["EXECUTING", "COMPLETED", "FAILED"],
|
|
2796
|
+
description: "New status: EXECUTING (starting), COMPLETED (done), or FAILED (error)"
|
|
2797
|
+
},
|
|
2798
|
+
executionSummary: {
|
|
2799
|
+
type: "string",
|
|
2800
|
+
description: "Summary of actions taken. REQUIRED when status is COMPLETED."
|
|
2801
|
+
}
|
|
2802
|
+
},
|
|
2803
|
+
required: ["projectId", "taskId", "status"]
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
];
|
|
2807
|
+
var RESOURCES = [
|
|
2808
|
+
{
|
|
2809
|
+
uri: "rigstate://project_morals",
|
|
2810
|
+
name: "project_morals",
|
|
2811
|
+
description: "Local project rules and standards from .rigstate/rules.md or similar files",
|
|
2812
|
+
mimeType: "text/markdown"
|
|
2813
|
+
}
|
|
2814
|
+
];
|
|
2815
|
+
async function validateWorkerKey() {
|
|
2816
|
+
try {
|
|
2817
|
+
const rootKey = await fs4.readFile(path4.join(process.cwd(), "worker.key"), "utf-8").catch(() => null);
|
|
2818
|
+
if (rootKey && rootKey.trim().length > 0) return true;
|
|
2819
|
+
const globalPath = path4.join(os.homedir(), ".rigstate", "auth.json");
|
|
2820
|
+
const globalContent = await fs4.readFile(globalPath, "utf-8").catch(() => null);
|
|
2821
|
+
if (globalContent) {
|
|
2822
|
+
const auth = JSON.parse(globalContent);
|
|
2823
|
+
if (auth.worker_token) return true;
|
|
2824
|
+
}
|
|
2825
|
+
} catch (e) {
|
|
2826
|
+
}
|
|
2827
|
+
return false;
|
|
2828
|
+
}
|
|
2829
|
+
async function fetchGovernancePolicy(apiUrl, apiKey, projectId) {
|
|
2830
|
+
try {
|
|
2831
|
+
const response = await fetch(`${apiUrl}/api/agent/policy?project_id=${projectId}&agent_name=frank`, {
|
|
2832
|
+
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
2833
|
+
});
|
|
2834
|
+
if (response.ok) return await response.json();
|
|
2835
|
+
} catch (e) {
|
|
2836
|
+
}
|
|
2837
|
+
return null;
|
|
2838
|
+
}
|
|
2839
|
+
async function main() {
|
|
2840
|
+
const apiKey = process.env.RIGSTATE_API_KEY;
|
|
2841
|
+
if (!apiKey) {
|
|
2842
|
+
console.error("\u274C Error: RIGSTATE_API_KEY environment variable is required.");
|
|
2843
|
+
console.error("");
|
|
2844
|
+
console.error("Get your API key from: https://rigstate.dev/settings/api-keys");
|
|
2845
|
+
console.error("Then set it: RIGSTATE_API_KEY=sk_rigstate_xxx npx @rigstate/mcp");
|
|
2846
|
+
process.exit(1);
|
|
2847
|
+
}
|
|
2848
|
+
const manifestPath = path4.join(process.cwd(), ".rigstate");
|
|
2849
|
+
const manifestContent = await fs4.readFile(manifestPath, "utf-8").catch(() => null);
|
|
2850
|
+
if (manifestContent) {
|
|
2851
|
+
try {
|
|
2852
|
+
const manifest = JSON.parse(manifestContent);
|
|
2853
|
+
if (manifest.project_id && manifest.api_url) {
|
|
2854
|
+
console.error(`\u{1F512} Governance: Fetching policy for Project ${manifest.project_id}...`);
|
|
2855
|
+
const policy = await fetchGovernancePolicy(manifest.api_url, apiKey, manifest.project_id);
|
|
2856
|
+
if (policy) {
|
|
2857
|
+
if (policy.allow_file_write === false) {
|
|
2858
|
+
process.env.RIGSTATE_GOVERNANCE_WRITE = "FALSE";
|
|
2859
|
+
console.error(`\u{1F512} Governance: Write Access DISABLED via Cloud Policy.`);
|
|
2860
|
+
} else {
|
|
2861
|
+
process.env.RIGSTATE_GOVERNANCE_WRITE = "TRUE";
|
|
2862
|
+
console.error(`\u{1F513} Governance: Write Access Enabled (Subject to Zero-Trust Key).`);
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
} catch (e) {
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
const authResult = await authenticateApiKey(apiKey);
|
|
2870
|
+
if (!authResult.success || !authResult.context) {
|
|
2871
|
+
console.error(`\u274C Authentication failed: ${authResult.error}`);
|
|
2872
|
+
process.exit(1);
|
|
2873
|
+
}
|
|
2874
|
+
const authContext = authResult.context;
|
|
2875
|
+
startFrankWatcher(authContext.supabase, authContext.userId);
|
|
2876
|
+
const server = new Server(
|
|
2877
|
+
{
|
|
2878
|
+
name: SERVER_NAME,
|
|
2879
|
+
version: SERVER_VERSION
|
|
2880
|
+
},
|
|
2881
|
+
{
|
|
2882
|
+
capabilities: {
|
|
2883
|
+
tools: {},
|
|
2884
|
+
resources: {}
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
);
|
|
2888
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2889
|
+
return {
|
|
2890
|
+
tools: [
|
|
2891
|
+
...TOOLS,
|
|
2892
|
+
...registry.getTools()
|
|
2893
|
+
]
|
|
2894
|
+
};
|
|
2895
|
+
});
|
|
2896
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
2897
|
+
return { resources: RESOURCES };
|
|
2898
|
+
});
|
|
2899
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
2900
|
+
const { uri } = request.params;
|
|
2901
|
+
if (uri === "rigstate://project_morals") {
|
|
2902
|
+
const morals = getProjectMorals();
|
|
2903
|
+
return {
|
|
2904
|
+
contents: [
|
|
2905
|
+
{
|
|
2906
|
+
uri,
|
|
2907
|
+
mimeType: "text/markdown",
|
|
2908
|
+
text: morals.formatted
|
|
2909
|
+
}
|
|
2910
|
+
]
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${uri}`);
|
|
2914
|
+
});
|
|
2915
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2916
|
+
try {
|
|
2917
|
+
const { name } = request.params;
|
|
2918
|
+
const args = request.params.arguments;
|
|
2919
|
+
try {
|
|
2920
|
+
return await registry.callTool(name, args, {
|
|
2921
|
+
supabase: authContext.supabase,
|
|
2922
|
+
userId: authContext.userId
|
|
2923
|
+
});
|
|
2924
|
+
} catch (e) {
|
|
2925
|
+
if (e.message && e.message.includes(`Tool '${name}' not found`)) {
|
|
2926
|
+
} else {
|
|
2927
|
+
throw new McpError(ErrorCode.InvalidParams, e.message);
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
const WRITE_TOOLS = [
|
|
2931
|
+
"write_to_file",
|
|
2932
|
+
"replace_file_content",
|
|
2933
|
+
"run_command",
|
|
2934
|
+
"save_decision",
|
|
2935
|
+
"submit_idea",
|
|
2936
|
+
"update_roadmap",
|
|
2937
|
+
"save_to_project_brain",
|
|
2938
|
+
"add_roadmap_chunk",
|
|
2939
|
+
"update_roadmap_status",
|
|
2940
|
+
"update_task_status"
|
|
2941
|
+
];
|
|
2942
|
+
if (WRITE_TOOLS.includes(name)) {
|
|
2943
|
+
if (process.env.RIGSTATE_GOVERNANCE_WRITE === "FALSE") {
|
|
2944
|
+
throw new McpError(ErrorCode.InvalidParams, "[GOVERNANCE_BLOCK]: Write access disabled via Rigstate Cloud.");
|
|
2945
|
+
}
|
|
2946
|
+
const isWorker = await validateWorkerKey();
|
|
2947
|
+
if (!isWorker) {
|
|
2948
|
+
throw new McpError(ErrorCode.InvalidParams, "[RIGSTATE_SECURITY_BLOCK]: Unauthorized attempt. No active Worker context detected.");
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
switch (name) {
|
|
2952
|
+
case "get_project_context": {
|
|
2953
|
+
const parsed = GetProjectContextInputSchema.parse(args);
|
|
2954
|
+
const result = await getProjectContext(
|
|
2955
|
+
authContext.supabase,
|
|
2956
|
+
authContext.userId,
|
|
2957
|
+
parsed.projectId
|
|
2958
|
+
);
|
|
2959
|
+
return {
|
|
2960
|
+
content: [
|
|
2961
|
+
{
|
|
2962
|
+
type: "text",
|
|
2963
|
+
text: result.summary
|
|
2964
|
+
}
|
|
2965
|
+
]
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
case "query_brain": {
|
|
2969
|
+
const parsed = QueryBrainInputSchema.parse(args);
|
|
2970
|
+
const result = await queryBrain(
|
|
2971
|
+
authContext.supabase,
|
|
2972
|
+
authContext.userId,
|
|
2973
|
+
parsed.projectId,
|
|
2974
|
+
parsed.query,
|
|
2975
|
+
parsed.limit,
|
|
2976
|
+
parsed.threshold
|
|
2977
|
+
);
|
|
2978
|
+
return {
|
|
2979
|
+
content: [
|
|
2980
|
+
{
|
|
2981
|
+
type: "text",
|
|
2982
|
+
text: result.formatted
|
|
2983
|
+
}
|
|
2984
|
+
]
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
case "get_latest_decisions": {
|
|
2988
|
+
const parsed = GetLatestDecisionsInputSchema.parse(args);
|
|
2989
|
+
const result = await getLatestDecisions(
|
|
2990
|
+
authContext.supabase,
|
|
2991
|
+
authContext.userId,
|
|
2992
|
+
parsed.projectId,
|
|
2993
|
+
parsed.limit
|
|
2994
|
+
);
|
|
2995
|
+
return {
|
|
2996
|
+
content: [
|
|
2997
|
+
{
|
|
2998
|
+
type: "text",
|
|
2999
|
+
text: result.summary
|
|
3000
|
+
}
|
|
3001
|
+
]
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
case "list_roadmap_tasks": {
|
|
3005
|
+
const parsed = ListRoadmapTasksInputSchema.parse(args);
|
|
3006
|
+
const result = await listRoadmapTasks(
|
|
3007
|
+
authContext.supabase,
|
|
3008
|
+
authContext.userId,
|
|
3009
|
+
parsed.projectId
|
|
3010
|
+
);
|
|
3011
|
+
return {
|
|
3012
|
+
content: [
|
|
3013
|
+
{
|
|
3014
|
+
type: "text",
|
|
3015
|
+
text: result.formatted
|
|
3016
|
+
}
|
|
3017
|
+
]
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
case "check_agent_bridge": {
|
|
3021
|
+
const parsed = CheckAgentBridgeInputSchema.parse(args);
|
|
3022
|
+
const result = await checkAgentBridge(
|
|
3023
|
+
authContext.supabase,
|
|
3024
|
+
parsed.projectId,
|
|
3025
|
+
parsed.action,
|
|
3026
|
+
parsed.bridgeId,
|
|
3027
|
+
parsed.status,
|
|
3028
|
+
parsed.summary,
|
|
3029
|
+
parsed.proposal,
|
|
3030
|
+
// @ts-ignore - execution_summary is a new optional prop
|
|
3031
|
+
parsed.execution_summary
|
|
3032
|
+
);
|
|
3033
|
+
return {
|
|
3034
|
+
content: [
|
|
3035
|
+
{
|
|
3036
|
+
type: "text",
|
|
3037
|
+
text: JSON.stringify(result, null, 2)
|
|
3038
|
+
}
|
|
3039
|
+
]
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
case "check_rules_sync": {
|
|
3043
|
+
const parsed = CheckRulesSyncInputSchema.parse(args);
|
|
3044
|
+
const result = await checkRulesSync(
|
|
3045
|
+
authContext.supabase,
|
|
3046
|
+
parsed.projectId,
|
|
3047
|
+
parsed.currentRulesContent
|
|
3048
|
+
);
|
|
3049
|
+
return {
|
|
3050
|
+
content: [
|
|
3051
|
+
{
|
|
3052
|
+
type: "text",
|
|
3053
|
+
text: JSON.stringify(result, null, 2)
|
|
3054
|
+
}
|
|
3055
|
+
]
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
case "get_next_roadmap_step": {
|
|
3059
|
+
const parsed = GetNextRoadmapStepInputSchema.parse(args);
|
|
3060
|
+
const result = await getNextRoadmapStep(
|
|
3061
|
+
authContext.supabase,
|
|
3062
|
+
parsed.projectId,
|
|
3063
|
+
parsed.currentStepId
|
|
3064
|
+
);
|
|
3065
|
+
return {
|
|
3066
|
+
content: [
|
|
3067
|
+
{
|
|
3068
|
+
type: "text",
|
|
3069
|
+
text: JSON.stringify(result, null, 2)
|
|
3070
|
+
}
|
|
3071
|
+
]
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
case "complete_roadmap_task": {
|
|
3075
|
+
const args2 = request.params.arguments;
|
|
3076
|
+
if (!args2.projectId || !args2.summary) {
|
|
3077
|
+
throw new Error("projectId and summary are required");
|
|
3078
|
+
}
|
|
3079
|
+
const result = await completeRoadmapTask(
|
|
3080
|
+
authContext.supabase,
|
|
3081
|
+
args2.projectId,
|
|
3082
|
+
args2.summary,
|
|
3083
|
+
args2.taskId,
|
|
3084
|
+
args2.gitDiff
|
|
3085
|
+
);
|
|
3086
|
+
return {
|
|
3087
|
+
content: [{
|
|
3088
|
+
type: "text",
|
|
3089
|
+
text: result.message
|
|
3090
|
+
}]
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
case "generate_professional_pdf": {
|
|
3094
|
+
const parsed = GenerateProfessionalPDFInputSchema.parse(args);
|
|
3095
|
+
const result = await generateProfessionalPdf(
|
|
3096
|
+
authContext.supabase,
|
|
3097
|
+
authContext.userId,
|
|
3098
|
+
parsed.projectId,
|
|
3099
|
+
parsed.reportType
|
|
3100
|
+
);
|
|
3101
|
+
return {
|
|
3102
|
+
content: [
|
|
3103
|
+
{
|
|
3104
|
+
type: "text",
|
|
3105
|
+
text: JSON.stringify(result, null, 2)
|
|
3106
|
+
}
|
|
3107
|
+
]
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
case "get_agent_status": {
|
|
3111
|
+
return {
|
|
3112
|
+
content: [
|
|
3113
|
+
{
|
|
3114
|
+
type: "text",
|
|
3115
|
+
text: JSON.stringify(watcherState, null, 2)
|
|
3116
|
+
}
|
|
3117
|
+
]
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
// =============================================================
|
|
3121
|
+
// BRYNJAR - THE LIBRARIAN (Archaeological Operations)
|
|
3122
|
+
// =============================================================
|
|
3123
|
+
case "archaeological_scan": {
|
|
3124
|
+
const parsed = ArchaeologicalScanInputSchema.parse(args);
|
|
3125
|
+
const result = await performArchaeologicalScan(
|
|
3126
|
+
authContext.supabase,
|
|
3127
|
+
parsed.projectId,
|
|
3128
|
+
parsed.gitLog,
|
|
3129
|
+
parsed.fileTree
|
|
3130
|
+
);
|
|
3131
|
+
return {
|
|
3132
|
+
content: [
|
|
3133
|
+
{
|
|
3134
|
+
type: "text",
|
|
3135
|
+
text: JSON.stringify(result, null, 2)
|
|
3136
|
+
}
|
|
3137
|
+
]
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
case "import_ghost_features": {
|
|
3141
|
+
const parsed = ImportGhostFeaturesInputSchema.parse(args);
|
|
3142
|
+
const result = await importGhostFeatures(
|
|
3143
|
+
authContext.supabase,
|
|
3144
|
+
parsed.projectId,
|
|
3145
|
+
parsed.features
|
|
3146
|
+
);
|
|
3147
|
+
return {
|
|
3148
|
+
content: [
|
|
3149
|
+
{
|
|
3150
|
+
type: "text",
|
|
3151
|
+
text: JSON.stringify(result, null, 2)
|
|
3152
|
+
}
|
|
3153
|
+
]
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
// =============================================================
|
|
3157
|
+
// WRITE OPERATIONS (DEPRECATED - READ ONLY MODE)
|
|
3158
|
+
// =============================================================
|
|
3159
|
+
case "save_decision": {
|
|
3160
|
+
return {
|
|
3161
|
+
content: [{
|
|
3162
|
+
type: "text",
|
|
3163
|
+
text: "\u{1F6AB} [READ-ONLY MODE] save_decision is deprecated in MCP. Please use the Rigstate Dashboard to record decisions."
|
|
3164
|
+
}],
|
|
3165
|
+
isError: true
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
case "submit_idea": {
|
|
3169
|
+
return {
|
|
3170
|
+
content: [{
|
|
3171
|
+
type: "text",
|
|
3172
|
+
text: "\u{1F6AB} [READ-ONLY MODE] submit_idea is deprecated in MCP. Please use the Rigstate Dashboard (Idea Lab) to submit new ideas."
|
|
3173
|
+
}],
|
|
3174
|
+
isError: true
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
case "update_roadmap": {
|
|
3178
|
+
return {
|
|
3179
|
+
content: [{
|
|
3180
|
+
type: "text",
|
|
3181
|
+
text: '\u{1F6AB} [READ-ONLY MODE] update_roadmap is deprecated in MCP. Use the Rigstate CLI ("rigstate sync") or the Dashboard to update tasks.'
|
|
3182
|
+
}],
|
|
3183
|
+
isError: true
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
case "run_architecture_audit": {
|
|
3187
|
+
return {
|
|
3188
|
+
content: [{
|
|
3189
|
+
type: "text",
|
|
3190
|
+
text: '\u{1F6AB} [READ-ONLY MODE] run_architecture_audit in MCP is deprecated. Use the Rigstate CLI command "rigstate check" for deterministic architectural validation.'
|
|
3191
|
+
}],
|
|
3192
|
+
isError: true
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
// =============================================================
|
|
3196
|
+
// TEACHER MODE TOOLS
|
|
3197
|
+
// =============================================================
|
|
3198
|
+
case "refine_logic": {
|
|
3199
|
+
const parsed = RefineLogicInputSchema.parse(args);
|
|
3200
|
+
const result = await refineLogic(
|
|
3201
|
+
authContext.supabase,
|
|
3202
|
+
authContext.userId,
|
|
3203
|
+
parsed.projectId,
|
|
3204
|
+
parsed.originalReasoning,
|
|
3205
|
+
parsed.userCorrection,
|
|
3206
|
+
parsed.scope || "project"
|
|
3207
|
+
);
|
|
3208
|
+
return {
|
|
3209
|
+
content: [
|
|
3210
|
+
{
|
|
3211
|
+
type: "text",
|
|
3212
|
+
text: `${result.message}
|
|
3213
|
+
|
|
3214
|
+
Trace ID: ${result.traceId}`
|
|
3215
|
+
}
|
|
3216
|
+
]
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
case "get_learned_instructions": {
|
|
3220
|
+
const parsed = GetLearnedInstructionsInputSchema.parse(args);
|
|
3221
|
+
const result = await getLearnedInstructions(
|
|
3222
|
+
authContext.supabase,
|
|
3223
|
+
authContext.userId,
|
|
3224
|
+
parsed.projectId
|
|
3225
|
+
);
|
|
3226
|
+
return {
|
|
3227
|
+
content: [
|
|
3228
|
+
{
|
|
3229
|
+
type: "text",
|
|
3230
|
+
text: result.formatted
|
|
3231
|
+
}
|
|
3232
|
+
]
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
case "sync_ide_rules": {
|
|
3236
|
+
const { projectId } = GenerateCursorRulesInputSchema.parse(request.params.arguments);
|
|
3237
|
+
const result = await syncIdeRules(authContext.supabase, projectId);
|
|
3238
|
+
return {
|
|
3239
|
+
content: [{
|
|
3240
|
+
type: "text",
|
|
3241
|
+
text: `FileName: ${result.fileName}
|
|
3242
|
+
|
|
3243
|
+
Content:
|
|
3244
|
+
${result.content}`
|
|
3245
|
+
}]
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
default:
|
|
3249
|
+
throw new McpError(
|
|
3250
|
+
ErrorCode.MethodNotFound,
|
|
3251
|
+
`Unknown tool: ${name}`
|
|
3252
|
+
);
|
|
3253
|
+
// =============================================================
|
|
3254
|
+
// STRUCTURAL SHIELD (EINAR & SVEN)
|
|
3255
|
+
// =============================================================
|
|
3256
|
+
case "analyze_dependency_graph": {
|
|
3257
|
+
const parsed = AnalyzeDependencyGraphInputSchema.parse(args);
|
|
3258
|
+
const result = await analyzeDependencyGraph(parsed);
|
|
3259
|
+
return {
|
|
3260
|
+
content: [
|
|
3261
|
+
{
|
|
3262
|
+
type: "text",
|
|
3263
|
+
text: JSON.stringify(result, null, 2)
|
|
3264
|
+
}
|
|
3265
|
+
]
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
case "audit_rls_status": {
|
|
3269
|
+
const parsed = AuditRlsStatusInputSchema.parse(args);
|
|
3270
|
+
const result = await auditRlsStatus(authContext.supabase, parsed);
|
|
3271
|
+
return {
|
|
3272
|
+
content: [
|
|
3273
|
+
{
|
|
3274
|
+
type: "text",
|
|
3275
|
+
text: JSON.stringify(result, null, 2)
|
|
3276
|
+
}
|
|
3277
|
+
]
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
// =============================================================
|
|
3281
|
+
// INTELLIGENCE CORE (MAJA & ASTRID)
|
|
3282
|
+
// =============================================================
|
|
3283
|
+
case "query_project_brain": {
|
|
3284
|
+
const parsed = QueryProjectBrainInputSchema.parse(args);
|
|
3285
|
+
const result = await queryProjectBrain(
|
|
3286
|
+
authContext.supabase,
|
|
3287
|
+
authContext.userId,
|
|
3288
|
+
parsed
|
|
3289
|
+
);
|
|
3290
|
+
return {
|
|
3291
|
+
content: [
|
|
3292
|
+
{
|
|
3293
|
+
type: "text",
|
|
3294
|
+
text: JSON.stringify(result, null, 2)
|
|
3295
|
+
}
|
|
3296
|
+
]
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
case "fetch_package_health": {
|
|
3300
|
+
const parsed = FetchPackageHealthInputSchema.parse(args);
|
|
3301
|
+
const result = await fetchPackageHealth(parsed);
|
|
3302
|
+
return {
|
|
3303
|
+
content: [
|
|
3304
|
+
{
|
|
3305
|
+
type: "text",
|
|
3306
|
+
text: JSON.stringify(result, null, 2)
|
|
3307
|
+
}
|
|
3308
|
+
]
|
|
3309
|
+
};
|
|
3310
|
+
}
|
|
3311
|
+
// =============================================================
|
|
3312
|
+
// ACTIVE MEMORY & PLANNING (MAJA & KINE)
|
|
3313
|
+
// =============================================================
|
|
3314
|
+
case "save_to_project_brain":
|
|
3315
|
+
case "update_roadmap_status":
|
|
3316
|
+
case "add_roadmap_chunk": {
|
|
3317
|
+
return {
|
|
3318
|
+
content: [{
|
|
3319
|
+
type: "text",
|
|
3320
|
+
text: "\u{1F6AB} [READ-ONLY MODE] Planning and active memory writes are deprecated in MCP. Use the Rigstate CLI or Dashboard."
|
|
3321
|
+
}],
|
|
3322
|
+
isError: true
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
// =============================================================
|
|
3326
|
+
// UI/UX & RESEARCH (LINUS & ASTRID)
|
|
3327
|
+
// =============================================================
|
|
3328
|
+
case "analyze_ui_component": {
|
|
3329
|
+
const parsed = AnalyzeUiComponentInputSchema.parse(args);
|
|
3330
|
+
const result = await analyzeUiComponent(parsed);
|
|
3331
|
+
return {
|
|
3332
|
+
content: [
|
|
3333
|
+
{
|
|
3334
|
+
type: "text",
|
|
3335
|
+
text: JSON.stringify(result, null, 2)
|
|
3336
|
+
}
|
|
3337
|
+
]
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
case "apply_design_system": {
|
|
3341
|
+
const parsed = ApplyDesignSystemInputSchema.parse(args);
|
|
3342
|
+
const result = await applyDesignSystem(parsed);
|
|
3343
|
+
return {
|
|
3344
|
+
content: [
|
|
3345
|
+
{
|
|
3346
|
+
type: "text",
|
|
3347
|
+
text: JSON.stringify(result, null, 2)
|
|
3348
|
+
}
|
|
3349
|
+
]
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
case "fetch_ui_library_docs": {
|
|
3353
|
+
const parsed = FetchUiLibraryDocsInputSchema.parse(args);
|
|
3354
|
+
const result = await fetchUiLibraryDocs(parsed);
|
|
3355
|
+
return {
|
|
3356
|
+
content: [
|
|
3357
|
+
{
|
|
3358
|
+
type: "text",
|
|
3359
|
+
text: JSON.stringify(result, null, 2)
|
|
3360
|
+
}
|
|
3361
|
+
]
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
// =========================================================
|
|
3365
|
+
// PENDING TASKS (IDE Integration)
|
|
3366
|
+
// =========================================================
|
|
3367
|
+
case "get_pending_tasks": {
|
|
3368
|
+
const parsed = GetPendingTasksInputSchema.parse(args);
|
|
3369
|
+
const result = await getPendingTasks(
|
|
3370
|
+
authContext.supabase,
|
|
3371
|
+
parsed.projectId
|
|
3372
|
+
);
|
|
3373
|
+
return {
|
|
3374
|
+
content: [
|
|
3375
|
+
{
|
|
3376
|
+
type: "text",
|
|
3377
|
+
text: JSON.stringify(result, null, 2)
|
|
3378
|
+
}
|
|
3379
|
+
]
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
case "update_task_status": {
|
|
3383
|
+
const parsed = UpdateTaskStatusInputSchema.parse(args);
|
|
3384
|
+
const result = await updateTaskStatus(
|
|
3385
|
+
authContext.supabase,
|
|
3386
|
+
parsed.projectId,
|
|
3387
|
+
parsed.taskId,
|
|
3388
|
+
parsed.status,
|
|
3389
|
+
parsed.executionSummary
|
|
3390
|
+
);
|
|
3391
|
+
return {
|
|
3392
|
+
content: [
|
|
3393
|
+
{
|
|
3394
|
+
type: "text",
|
|
3395
|
+
text: JSON.stringify(result, null, 2)
|
|
3396
|
+
}
|
|
3397
|
+
]
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
} catch (error) {
|
|
3402
|
+
if (error.name === "ZodError") {
|
|
3403
|
+
throw new McpError(
|
|
3404
|
+
ErrorCode.InvalidParams,
|
|
3405
|
+
`Invalid parameters: ${error.errors.map((e) => e.message).join(", ")}`
|
|
3406
|
+
);
|
|
3407
|
+
}
|
|
3408
|
+
if (error.message?.includes("access denied") || error.message?.includes("not found")) {
|
|
3409
|
+
throw new McpError(
|
|
3410
|
+
ErrorCode.InvalidParams,
|
|
3411
|
+
error.message
|
|
3412
|
+
);
|
|
3413
|
+
}
|
|
3414
|
+
if (error.message?.includes("Permission denied") || error.message?.includes("constraint") || error.message?.includes("already exists")) {
|
|
3415
|
+
throw new McpError(
|
|
3416
|
+
ErrorCode.InvalidParams,
|
|
3417
|
+
`Write operation failed: ${error.message}`
|
|
3418
|
+
);
|
|
3419
|
+
}
|
|
3420
|
+
if (error.message?.includes("Failed to")) {
|
|
3421
|
+
throw new McpError(
|
|
3422
|
+
ErrorCode.InternalError,
|
|
3423
|
+
error.message
|
|
3424
|
+
);
|
|
3425
|
+
}
|
|
3426
|
+
if (error instanceof McpError) {
|
|
3427
|
+
throw error;
|
|
3428
|
+
}
|
|
3429
|
+
throw new McpError(
|
|
3430
|
+
ErrorCode.InternalError,
|
|
3431
|
+
error.message || "An unexpected error occurred"
|
|
3432
|
+
);
|
|
3433
|
+
}
|
|
3434
|
+
});
|
|
3435
|
+
const transport = new StdioServerTransport();
|
|
3436
|
+
await server.connect(transport);
|
|
3437
|
+
console.error(`\u2705 Rigstate MCP Server v${SERVER_VERSION} started`);
|
|
3438
|
+
console.error(` User ID: ${authContext.userId}`);
|
|
3439
|
+
console.error(` Mode: Read-Only (write ops deprecated)`);
|
|
3440
|
+
}
|
|
3441
|
+
main().catch((error) => {
|
|
3442
|
+
console.error("Fatal error:", error);
|
|
3443
|
+
process.exit(1);
|
|
3444
|
+
});
|
|
3445
|
+
//# sourceMappingURL=index.js.map
|