@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.
Files changed (40) hide show
  1. package/.env.example +8 -0
  2. package/README.md +352 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +3445 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +43 -0
  7. package/roadmap.json +531 -0
  8. package/src/agents/the-scribe.ts +122 -0
  9. package/src/index.ts +1792 -0
  10. package/src/lib/supabase.ts +120 -0
  11. package/src/lib/tool-registry.ts +134 -0
  12. package/src/lib/types.ts +415 -0
  13. package/src/lib/utils.ts +10 -0
  14. package/src/resources/project-morals.ts +92 -0
  15. package/src/tools/arch-tools.ts +166 -0
  16. package/src/tools/archaeological-scan.ts +335 -0
  17. package/src/tools/check-agent-bridge.ts +169 -0
  18. package/src/tools/check-rules-sync.ts +85 -0
  19. package/src/tools/complete-roadmap-task.ts +96 -0
  20. package/src/tools/generate-professional-pdf.ts +232 -0
  21. package/src/tools/get-latest-decisions.ts +130 -0
  22. package/src/tools/get-next-roadmap-step.ts +76 -0
  23. package/src/tools/get-project-context.ts +163 -0
  24. package/src/tools/index.ts +17 -0
  25. package/src/tools/list-features.ts +67 -0
  26. package/src/tools/list-roadmap-tasks.ts +61 -0
  27. package/src/tools/pending-tasks.ts +228 -0
  28. package/src/tools/planning-tools.ts +123 -0
  29. package/src/tools/query-brain.ts +125 -0
  30. package/src/tools/research-tools.ts +149 -0
  31. package/src/tools/run-architecture-audit.ts +203 -0
  32. package/src/tools/save-decision.ts +77 -0
  33. package/src/tools/security-tools.ts +82 -0
  34. package/src/tools/submit-idea.ts +66 -0
  35. package/src/tools/sync-ide-rules.ts +76 -0
  36. package/src/tools/teacher-mode.ts +171 -0
  37. package/src/tools/ui-tools.ts +191 -0
  38. package/src/tools/update-roadmap.ts +105 -0
  39. package/tsconfig.json +29 -0
  40. package/tsup.config.ts +16 -0
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Tool: get_latest_decisions
3
+ *
4
+ * Fetches the most recent ADRs and decisions from The Council,
5
+ * including active roadmap steps and council session feedback.
6
+ */
7
+
8
+ import { SupabaseClient } from '@supabase/supabase-js';
9
+ import type { DecisionsResponse, CouncilSession, RoadmapStep } from '../lib/types.js';
10
+
11
+ export async function getLatestDecisions(
12
+ supabase: SupabaseClient,
13
+ userId: string,
14
+ projectId: string,
15
+ limit: number = 5
16
+ ): Promise<DecisionsResponse> {
17
+ // First, verify project ownership
18
+ const { data: project, error: projectError } = await supabase
19
+ .from('projects')
20
+ .select('id')
21
+ .eq('id', projectId)
22
+ .eq('owner_id', userId)
23
+ .single();
24
+
25
+ if (projectError || !project) {
26
+ throw new Error('Project not found or access denied');
27
+ }
28
+
29
+ // Fetch council sessions
30
+ const { data: sessionData, error: sessionError } = await supabase
31
+ .from('council_sessions')
32
+ .select('id, project_id, recruited_agents, feedback_summary, duration_ms, sprints_count, tasks_count, created_at')
33
+ .eq('project_id', projectId)
34
+ .order('created_at', { ascending: false })
35
+ .limit(limit);
36
+
37
+ const sessions: CouncilSession[] = (sessionData || []).map(s => ({
38
+ id: s.id,
39
+ projectId: s.project_id,
40
+ recruitedAgents: s.recruited_agents || [],
41
+ feedbackSummary: (s.feedback_summary || []).map((f: any) => ({
42
+ agentName: f.agentName || f.agent?.name || 'Unknown',
43
+ emoji: f.emoji || f.agent?.emoji || '🤖',
44
+ critiques: f.critiques || [],
45
+ approved: f.approved ?? true
46
+ })),
47
+ durationMs: s.duration_ms,
48
+ sprintsCount: s.sprints_count,
49
+ tasksCount: s.tasks_count,
50
+ createdAt: s.created_at
51
+ }));
52
+
53
+ // Fetch active roadmap step
54
+ const { data: activeStep, error: stepError } = await supabase
55
+ .from('roadmap_chunks')
56
+ .select('id, step_number, title, status, sprint_focus')
57
+ .eq('project_id', projectId)
58
+ .eq('status', 'ACTIVE')
59
+ .maybeSingle();
60
+
61
+ const activeRoadmapStep: RoadmapStep | null = activeStep
62
+ ? {
63
+ id: activeStep.id,
64
+ stepNumber: activeStep.step_number,
65
+ title: activeStep.title,
66
+ status: activeStep.status,
67
+ sprintFocus: activeStep.sprint_focus
68
+ }
69
+ : null;
70
+
71
+ // Build summary
72
+ const summaryParts: string[] = [];
73
+
74
+ // Add active step info
75
+ if (activeRoadmapStep) {
76
+ summaryParts.push(`=== CURRENT FOCUS ===`);
77
+ summaryParts.push(`Step ${activeRoadmapStep.stepNumber}: ${activeRoadmapStep.title}`);
78
+ if (activeRoadmapStep.sprintFocus) {
79
+ summaryParts.push(`Focus: ${activeRoadmapStep.sprintFocus}`);
80
+ }
81
+ summaryParts.push('');
82
+ }
83
+
84
+ // Add recent council decisions
85
+ if (sessions.length > 0) {
86
+ summaryParts.push(`=== RECENT COUNCIL SESSIONS (${sessions.length}) ===`);
87
+
88
+ for (const session of sessions) {
89
+ const date = new Date(session.createdAt).toLocaleDateString();
90
+ summaryParts.push(`\n📅 Session on ${date}`);
91
+ summaryParts.push(` Agents: ${session.recruitedAgents.join(', ')}`);
92
+
93
+ if (session.feedbackSummary.length > 0) {
94
+ summaryParts.push(` Key Feedback:`);
95
+ for (const feedback of session.feedbackSummary.slice(0, 3)) {
96
+ const status = feedback.approved ? '✅' : '⚠️';
97
+ summaryParts.push(` ${feedback.emoji} ${feedback.agentName}: ${status}`);
98
+ if (feedback.critiques.length > 0) {
99
+ summaryParts.push(` - ${feedback.critiques[0]}`);
100
+ }
101
+ }
102
+ }
103
+ }
104
+ } else {
105
+ summaryParts.push(`No council sessions recorded yet.`);
106
+ }
107
+
108
+ // Fetch decision-type memories for additional context
109
+ const { data: decisionMemories } = await supabase
110
+ .from('project_memories')
111
+ .select('content, category, created_at')
112
+ .eq('project_id', projectId)
113
+ .eq('is_active', true)
114
+ .in('category', ['decision', 'architecture', 'constraint'])
115
+ .order('created_at', { ascending: false })
116
+ .limit(5);
117
+
118
+ if (decisionMemories && decisionMemories.length > 0) {
119
+ summaryParts.push(`\n=== KEY DECISIONS FROM BRAIN ===`);
120
+ for (const memory of decisionMemories) {
121
+ summaryParts.push(`- [${memory.category.toUpperCase()}] ${memory.content}`);
122
+ }
123
+ }
124
+
125
+ return {
126
+ sessions,
127
+ activeRoadmapStep,
128
+ summary: summaryParts.join('\n')
129
+ };
130
+ }
@@ -0,0 +1,76 @@
1
+
2
+ import { SupabaseClient } from '@supabase/supabase-js';
3
+ import type { RoadmapChunk } from '../lib/types.js';
4
+
5
+ export interface GetNextRoadmapStepResponse {
6
+ nextStep: RoadmapChunk | null;
7
+ message: string;
8
+ }
9
+
10
+ export async function getNextRoadmapStep(
11
+ supabase: SupabaseClient,
12
+ projectId: string,
13
+ currentStepId?: string
14
+ ): Promise<GetNextRoadmapStepResponse> {
15
+
16
+ // Strategy:
17
+ // 1. If currentStepId is provided, look for the step with the next higher step_number.
18
+ // 2. If not, look for the first 'ACTIVE' step.
19
+ // 3. If no ACTIVE, look for the first 'LOCKED' step.
20
+
21
+ let currentStepNumber = 0;
22
+
23
+ if (currentStepId) {
24
+ const { data: current } = await supabase
25
+ .from('roadmap_chunks')
26
+ .select('step_number')
27
+ .eq('id', currentStepId)
28
+ .single();
29
+
30
+ if (current) {
31
+ currentStepNumber = current.step_number;
32
+ }
33
+ } else {
34
+ // Try to find currently active step to establish baseline
35
+ const { data: active } = await supabase
36
+ .from('roadmap_chunks')
37
+ .select('step_number')
38
+ .eq('project_id', projectId)
39
+ .eq('status', 'ACTIVE')
40
+ .order('step_number', { ascending: true })
41
+ .limit(1)
42
+ .single();
43
+
44
+ if (active) {
45
+ currentStepNumber = active.step_number;
46
+ }
47
+ }
48
+
49
+ // Find the next step > currentStepNumber
50
+ // We prioritize LOCKED or PENDING steps that come *after* the current one.
51
+ const { data: nextStep, error } = await supabase
52
+ .from('roadmap_chunks')
53
+ .select('*')
54
+ .eq('project_id', projectId)
55
+ .gt('step_number', currentStepNumber)
56
+ .neq('status', 'COMPLETED') // We want the next *workable* step
57
+ .order('step_number', { ascending: true })
58
+ .limit(1)
59
+ .single();
60
+
61
+ if (error && error.code !== 'PGRST116') { // PGRST116 is "Row not found" which is fine
62
+ throw new Error(`Failed to fetch next roadmap step: ${error.message}`);
63
+ }
64
+
65
+ if (!nextStep) {
66
+ return {
67
+ nextStep: null,
68
+ message: "No further steps found in the roadmap. You might be done! 🎉"
69
+ };
70
+ }
71
+
72
+ return {
73
+ nextStep: nextStep as RoadmapChunk,
74
+ message: `Next step found: [Step ${nextStep.step_number}] ${nextStep.title}`
75
+ };
76
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Tool: get_project_context
3
+ *
4
+ * Returns the project type, tech stack, and high-level description.
5
+ * This gives AI editors context about what they're working with.
6
+ */
7
+
8
+ import { SupabaseClient } from '@supabase/supabase-js';
9
+ import type { ProjectContextResponse, TechStackInfo } from '../lib/types.js';
10
+
11
+ // Key libraries we care about for display
12
+ const KEY_LIBS: Record<string, string> = {
13
+ 'next': 'Next.js',
14
+ 'react': 'React',
15
+ 'vue': 'Vue',
16
+ 'svelte': 'Svelte',
17
+ 'angular': 'Angular',
18
+ 'tailwindcss': 'Tailwind',
19
+ 'prisma': 'Prisma',
20
+ 'drizzle-orm': 'Drizzle',
21
+ '@supabase/supabase-js': 'Supabase',
22
+ 'stripe': 'Stripe',
23
+ 'openai': 'OpenAI',
24
+ 'typescript': 'TypeScript',
25
+ 'framer-motion': 'Framer Motion',
26
+ 'zod': 'Zod',
27
+ 'ai': 'Vercel AI',
28
+ };
29
+
30
+ export async function getProjectContext(
31
+ supabase: SupabaseClient,
32
+ userId: string,
33
+ projectId: string
34
+ ): Promise<ProjectContextResponse> {
35
+ // Fetch project with ownership check
36
+ const { data: project, error: projectError } = await supabase
37
+ .from('projects')
38
+ .select('id, name, description, project_type, created_at, last_indexed_at, detected_stack, repository_tree')
39
+ .eq('id', projectId)
40
+ .eq('owner_id', userId)
41
+ .single();
42
+
43
+ if (projectError || !project) {
44
+ throw new Error('Project not found or access denied');
45
+ }
46
+
47
+ // Fetch Digest Data
48
+ const { data: agentTasks } = await supabase
49
+ .from('agent_bridge')
50
+ .select('id, roadmap_chunks(title), execution_summary, completed_at')
51
+ .eq('project_id', projectId)
52
+ .eq('status', 'COMPLETED')
53
+ .order('completed_at', { ascending: false })
54
+ .limit(3);
55
+
56
+ const { data: roadmapItems } = await supabase
57
+ .from('roadmap_chunks')
58
+ .select('title, status, updated_at')
59
+ .eq('project_id', projectId)
60
+ .order('updated_at', { ascending: false })
61
+ .limit(3);
62
+
63
+ // Parse tech stack from detected_stack
64
+ const techStack: TechStackInfo = {
65
+ framework: null,
66
+ orm: null,
67
+ database: null,
68
+ keyLibraries: [],
69
+ topFolders: []
70
+ };
71
+
72
+ if (project.detected_stack) {
73
+ const stack = project.detected_stack as {
74
+ dependencies?: Record<string, string>;
75
+ devDependencies?: Record<string, string>;
76
+ };
77
+ const allDeps = { ...stack.dependencies, ...stack.devDependencies };
78
+
79
+ for (const [name, version] of Object.entries(allDeps)) {
80
+ const cleanVersion = (version as string).replace(/[\^~]/g, '');
81
+
82
+ // Categorize
83
+ if (name === 'next' || name === 'react' || name === 'vue' || name === 'angular' || name === 'svelte') {
84
+ if (!techStack.framework) {
85
+ techStack.framework = `${KEY_LIBS[name] || name} ${cleanVersion}`;
86
+ }
87
+ } else if (name.includes('prisma') || name.includes('drizzle') || name.includes('typeorm')) {
88
+ if (!techStack.orm) {
89
+ techStack.orm = `${KEY_LIBS[name] || name} ${cleanVersion}`;
90
+ }
91
+ } else if (KEY_LIBS[name]) {
92
+ techStack.keyLibraries.push(`${KEY_LIBS[name]} ${cleanVersion}`);
93
+ }
94
+ }
95
+ }
96
+
97
+ // Extract top folders from repository tree
98
+ if (project.repository_tree && Array.isArray(project.repository_tree)) {
99
+ techStack.topFolders = [...new Set(
100
+ project.repository_tree
101
+ .filter((t: any) => t.type === 'tree' && !t.path.includes('/'))
102
+ .map((t: any) => t.path)
103
+ )].slice(0, 10) as string[];
104
+ }
105
+
106
+ // Build summary
107
+ const summaryParts: string[] = [];
108
+
109
+ if (project.project_type) {
110
+ summaryParts.push(`Project Type: ${project.project_type.toUpperCase()}`);
111
+ }
112
+
113
+ if (techStack.framework) {
114
+ summaryParts.push(`Framework: ${techStack.framework}`);
115
+ }
116
+
117
+ if (techStack.orm) {
118
+ summaryParts.push(`ORM: ${techStack.orm}`);
119
+ }
120
+
121
+ if (techStack.keyLibraries.length > 0) {
122
+ summaryParts.push(`Key Libraries: ${techStack.keyLibraries.join(', ')}`);
123
+ }
124
+
125
+ if (techStack.topFolders.length > 0) {
126
+ summaryParts.push(`Top Folders: ${techStack.topFolders.join(', ')}`);
127
+ }
128
+
129
+ if (project.description) {
130
+ summaryParts.push(`\nDescription: ${project.description}`);
131
+ }
132
+
133
+ // Add Digest to Summary
134
+ summaryParts.push('\n=== RECENT ACTIVITY DIGEST ===');
135
+
136
+ if (agentTasks && agentTasks.length > 0) {
137
+ summaryParts.push('\nLatest AI Executions:');
138
+ agentTasks.forEach((t: any) => {
139
+ const time = t.completed_at ? new Date(t.completed_at).toLocaleString() : 'Recently';
140
+ summaryParts.push(`- [${time}] ${t.roadmap_chunks?.title || 'Task'}: ${t.execution_summary || 'Completed'}`);
141
+ });
142
+ }
143
+
144
+ if (roadmapItems && roadmapItems.length > 0) {
145
+ summaryParts.push('\nRoadmap Updates:');
146
+ roadmapItems.forEach((i: any) => {
147
+ summaryParts.push(`- ${i.title} is now ${i.status}`);
148
+ });
149
+ }
150
+
151
+ return {
152
+ project: {
153
+ id: project.id,
154
+ name: project.name,
155
+ description: project.description,
156
+ projectType: project.project_type,
157
+ createdAt: project.created_at,
158
+ lastIndexedAt: project.last_indexed_at
159
+ },
160
+ techStack,
161
+ summary: summaryParts.join('\n') || 'No project context available.'
162
+ };
163
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tools Index
3
+ *
4
+ * Re-exports all MCP tools for easy importing.
5
+ */
6
+
7
+ // Read operations
8
+ export { getProjectContext } from './get-project-context.js';
9
+ export { queryBrain } from './query-brain.js';
10
+ export { getLatestDecisions } from './get-latest-decisions.js';
11
+
12
+ // Write operations
13
+ export { saveDecision } from './save-decision.js';
14
+ export { submitIdea } from './submit-idea.js';
15
+ export { updateRoadmap } from './update-roadmap.js';
16
+ export { runArchitectureAudit } from './run-architecture-audit.js';
17
+
@@ -0,0 +1,67 @@
1
+ import { z } from 'zod';
2
+ import { ToolDefinition } from '../lib/tool-registry.js';
3
+
4
+ /**
5
+ * Input Schema for List Features
6
+ */
7
+ const InputSchema = z.object({
8
+ projectId: z.string().uuid().describe('The UUID of the Rigstate project')
9
+ });
10
+
11
+ /**
12
+ * Tool Definition: list_features
13
+ */
14
+ export const listFeaturesTool: ToolDefinition<typeof InputSchema> = {
15
+ name: 'list_features',
16
+ description: `Lists all high-level features (epics) for a project.
17
+ Useful for understanding the strategic context and major milestones.`,
18
+ schema: InputSchema,
19
+ handler: async ({ projectId }, { supabase, userId }) => {
20
+ // 1. Verify project ownership
21
+ const { data: project, error: projectError } = await supabase
22
+ .from('projects')
23
+ .select('id')
24
+ .eq('id', projectId)
25
+ .eq('owner_id', userId)
26
+ .single();
27
+
28
+ if (projectError || !project) {
29
+ throw new Error('Project not found or access denied');
30
+ }
31
+
32
+ // 2. Fetch features
33
+ const { data: features, error } = await supabase
34
+ .from('features')
35
+ .select('id, name, description, priority, status')
36
+ .eq('project_id', projectId)
37
+ .neq('status', 'ARCHIVED')
38
+ .order('created_at', { ascending: false }); // Sort by newest (or priority if column existed properly typed)
39
+
40
+ // Note: 'horizon' was not in my simple schema mental model, removed to be safe or use 'status' as proxy.
41
+ // Assuming strict schema adherence.
42
+ // If priority column is text, sorting by it strictly might be weird if not enum.
43
+ // Let's trust 'created_at' for stability for now.
44
+
45
+ if (error) {
46
+ throw new Error(`Failed to fetch features: ${error.message}`);
47
+ }
48
+
49
+ // 3. Format response
50
+ const formatted = (features || []).length > 0
51
+ ? (features || []).map(f => {
52
+ const priorityStr = f.priority === 'MVP' ? '[MVP] ' : '';
53
+ return `- ${priorityStr}${f.name} (${f.status})`;
54
+ }).join('\n')
55
+ : 'No active features found.';
56
+
57
+ return {
58
+ content: [
59
+ {
60
+ type: 'text',
61
+ text: formatted
62
+ }
63
+ ]
64
+ };
65
+ }
66
+ };
67
+
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Tool: list_roadmap_tasks
3
+ *
4
+ * Lists all roadmap tasks for a project that are not completed.
5
+ */
6
+
7
+ import { SupabaseClient } from '@supabase/supabase-js';
8
+ import type { ListRoadmapTasksResponse } from '../lib/types.js';
9
+
10
+ export async function listRoadmapTasks(
11
+ supabase: SupabaseClient,
12
+ userId: string,
13
+ projectId: string
14
+ ): Promise<ListRoadmapTasksResponse> {
15
+ // 1. Verify project ownership
16
+ const { data: project, error: projectError } = await supabase
17
+ .from('projects')
18
+ .select('id')
19
+ .eq('id', projectId)
20
+ .eq('owner_id', userId)
21
+ .single();
22
+
23
+ if (projectError || !project) {
24
+ throw new Error('Project not found or access denied');
25
+ }
26
+
27
+ // 2. Fetch non-completed tasks
28
+ const { data: tasks, error } = await supabase
29
+ .from('roadmap_chunks')
30
+ .select('id, title, priority, status, step_number, prompt_content')
31
+ .eq('project_id', projectId)
32
+ .neq('status', 'COMPLETED')
33
+ .order('priority', { ascending: false })
34
+ .order('step_number', { ascending: true });
35
+
36
+ if (error) {
37
+ console.error('Failed to fetch roadmap tasks:', error);
38
+ throw new Error('Failed to fetch roadmap tasks');
39
+ }
40
+
41
+ // 3. Format response
42
+ const formatted = (tasks || []).length > 0
43
+ ? (tasks || []).map(t => {
44
+ const statusEmoji = t.status === 'ACTIVE' ? '🔵' : '🔒';
45
+ const priorityStr = t.priority ? `[${t.priority}]` : '';
46
+ return `${statusEmoji} Step ${t.step_number}: ${t.title} (ID: ${t.id})`;
47
+ }).join('\n')
48
+ : 'No active or locked tasks found in the roadmap.';
49
+
50
+ return {
51
+ tasks: (tasks || []).map(t => ({
52
+ id: t.id,
53
+ title: t.title,
54
+ priority: t.priority,
55
+ status: t.status,
56
+ step_number: t.step_number,
57
+ prompt_content: t.prompt_content
58
+ })),
59
+ formatted
60
+ };
61
+ }