@rigstate/mcp 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigstate/mcp",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "Rigstate MCP Server - Model Context Protocol for AI Editors",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,6 +16,7 @@ Useful for transitioning between tasks.`,
16
16
  handler: async (args, context) => {
17
17
  const result = await getNextRoadmapStep(
18
18
  context.supabase,
19
+ context.userId,
19
20
  args.projectId,
20
21
  args.currentStepId
21
22
  );
@@ -30,58 +31,62 @@ export interface GetNextRoadmapStepResponse {
30
31
 
31
32
  export async function getNextRoadmapStep(
32
33
  supabase: SupabaseClient,
34
+ userId: string,
33
35
  projectId: string,
34
36
  currentStepId?: string
35
37
  ): Promise<GetNextRoadmapStepResponse> {
36
38
 
39
+ // 1. Fetch all tasks via secure RPC
40
+ const { data: allTasks, error: fetchError } = await supabase
41
+ .rpc('get_roadmap_chunks_secure', {
42
+ p_project_id: projectId,
43
+ p_user_id: userId
44
+ });
45
+
46
+ if (fetchError) {
47
+ throw new Error(`Failed to fetch roadmap data: ${fetchError.message}`);
48
+ }
49
+
50
+ const tasks = (allTasks || []) as any[];
51
+
37
52
  // Strategy:
38
53
  // 1. If currentStepId is provided, look for the step with the next higher step_number.
39
- // 2. If not, look for the first 'ACTIVE' step.
40
- // 3. If no ACTIVE, look for the first 'LOCKED' step.
54
+ // 2. If not, look for the first 'ACTIVE' or 'IN_PROGRESS' step. Baseline.
55
+ // 3. If no ACTIVE, current baseline is 0.
41
56
 
42
57
  let currentStepNumber = 0;
43
58
 
44
59
  if (currentStepId) {
45
- const { data: current } = await supabase
46
- .from('roadmap_chunks')
47
- .select('step_number')
48
- .eq('id', currentStepId)
49
- .single();
50
-
60
+ const current = tasks.find(t => t.id === currentStepId);
51
61
  if (current) {
52
62
  currentStepNumber = current.step_number;
53
63
  }
54
64
  } else {
55
- // Try to find currently active step to establish baseline
56
- const { data: active } = await supabase
57
- .from('roadmap_chunks')
58
- .select('step_number')
59
- .eq('project_id', projectId)
60
- .in('status', ['ACTIVE', 'IN_PROGRESS'])
61
- .order('step_number', { ascending: true })
62
- .limit(1)
63
- .single();
65
+ // Find baseline: first ACTIVE/IN_PROGRESS
66
+ const active = tasks
67
+ .filter(t => ['ACTIVE', 'IN_PROGRESS'].includes(t.status))
68
+ .sort((a, b) => a.step_number - b.step_number)[0];
64
69
 
65
70
  if (active) {
66
- currentStepNumber = active.step_number;
71
+ // If we are looking for the "next" step and we have an active one,
72
+ // we usually want to start *from* that active one or its successor.
73
+ // For now, let's keep the baseline logic: if we have an active one, we are *on* it.
74
+ // But get_next is often called to FIND what to do.
75
+ // If we have an active one, THAT is the next thing to work on.
76
+
77
+ // Logic change: if no currentStepId provided, and we have an ACTIVE one,
78
+ // that ACTIVE one IS the next step.
79
+ return {
80
+ nextStep: active as RoadmapChunk,
81
+ message: `Current active step: [Step ${active.step_number}] ${active.title}`
82
+ };
67
83
  }
68
84
  }
69
85
 
70
- // Find the next step > currentStepNumber
71
- // We prioritize LOCKED or PENDING steps that come *after* the current one.
72
- const { data: nextStep, error } = await supabase
73
- .from('roadmap_chunks')
74
- .select('*')
75
- .eq('project_id', projectId)
76
- .gt('step_number', currentStepNumber)
77
- .neq('status', 'COMPLETED') // We want the next *workable* step
78
- .order('step_number', { ascending: true })
79
- .limit(1)
80
- .single();
81
-
82
- if (error && error.code !== 'PGRST116') { // PGRST116 is "Row not found" which is fine
83
- throw new Error(`Failed to fetch next roadmap step: ${error.message}`);
84
- }
86
+ // Find the next step > currentStepNumber that is not COMPLETED
87
+ const nextStep = tasks
88
+ .filter(t => t.step_number > currentStepNumber && t.status !== 'COMPLETED')
89
+ .sort((a, b) => a.step_number - b.step_number)[0];
85
90
 
86
91
  if (!nextStep) {
87
92
  return {
@@ -94,43 +94,32 @@ export async function getProjectContext(
94
94
 
95
95
 
96
96
 
97
- // Continuity Loop: Fetch Active & Next Tasks
98
- const [activeTaskResult, nextTaskResult] = await Promise.all([
99
- supabase
100
- .from('roadmap_chunks')
101
- .select('id, title, step_number, role, instruction_set')
102
- .eq('project_id', projectId)
103
- .in('status', ['IN_PROGRESS', 'ACTIVE'])
104
- .limit(1)
105
- .maybeSingle(),
106
- supabase
107
- .from('roadmap_chunks')
108
- .select('id, title, step_number, role')
109
- .eq('project_id', projectId)
110
- .in('status', ['PENDING', 'LOCKED'])
111
- .order('step_number', { ascending: true })
112
- .limit(1)
113
- .maybeSingle()
114
- ]);
115
-
116
- const activeTask = activeTaskResult.data;
117
- const nextTask = nextTaskResult.data;
118
-
119
- // Fetch Digest Data (Reduced to 2 to save tokens)
97
+ // Continuity Loop: Fetch Active & Next Tasks via secure RPC
98
+ const { data: allChunks, error: chunksError } = await supabase
99
+ .rpc('get_roadmap_chunks_secure', {
100
+ p_project_id: projectId,
101
+ p_user_id: userId
102
+ });
103
+
104
+ const tasks = (allChunks || []) as any[];
105
+
106
+ // In-memory filtering to match original logic
107
+ const activeTask = tasks.find(t => ['IN_PROGRESS', 'ACTIVE'].includes(t.status));
108
+ const nextTask = tasks
109
+ .filter(t => ['PENDING', 'LOCKED'].includes(t.status))
110
+ .sort((a, b) => a.step_number - b.step_number)[0];
111
+
112
+ // Fetch Digest Data via secure RPC
120
113
  const { data: agentTasks } = await supabase
121
- .from('agent_bridge')
122
- .select('id, roadmap_chunks(title), execution_summary, completed_at')
123
- .eq('project_id', projectId)
124
- .eq('status', 'COMPLETED')
125
- .order('completed_at', { ascending: false })
126
- .limit(2);
127
-
128
- const { data: roadmapItems } = await supabase
129
- .from('roadmap_chunks')
130
- .select('title, status, updated_at')
131
- .eq('project_id', projectId)
132
- .order('updated_at', { ascending: false })
133
- .limit(2);
114
+ .rpc('get_agent_bridge_secure', {
115
+ p_project_id: projectId,
116
+ p_user_id: userId,
117
+ p_limit: 2
118
+ });
119
+
120
+ const roadmapItems = tasks
121
+ .sort((a, b) => new Date(b.updated_at || b.created_at).getTime() - new Date(a.updated_at || a.created_at).getTime())
122
+ .slice(0, 2);
134
123
 
135
124
  // Parse tech stack from detected_stack
136
125
  const techStack: TechStackInfo = {
@@ -239,7 +228,7 @@ export async function getProjectContext(
239
228
  summaryParts.push('\nLatest AI Executions:');
240
229
  agentTasks.forEach((t: any) => {
241
230
  const time = t.completed_at ? new Date(t.completed_at).toLocaleString() : 'Recently';
242
- summaryParts.push(`- [${time}] ${t.roadmap_chunks?.title || 'Task'}: ${t.execution_summary || 'Completed'}`);
231
+ summaryParts.push(`- [${time}] ${t.roadmap_title || 'Task'}: ${t.execution_summary || 'Completed'}`);
243
232
  });
244
233
  }
245
234
 
@@ -33,42 +33,42 @@ export async function listRoadmapTasks(
33
33
  userId: string,
34
34
  projectId: string
35
35
  ): Promise<ListRoadmapTasksResponse> {
36
- // 1. Verify project access
37
- const { data: hasAccess, error: accessError } = await supabase
38
- .rpc('check_project_access_secure', {
36
+ // 1. Fetch tasks via secure RPC (bypasses RLS)
37
+ const { data: tasks, error } = await supabase
38
+ .rpc('get_roadmap_chunks_secure', {
39
39
  p_project_id: projectId,
40
40
  p_user_id: userId
41
41
  });
42
42
 
43
- if (accessError || !hasAccess) {
44
- throw new Error('Project not found or access denied');
45
- }
46
-
47
- // 2. Fetch non-completed tasks
48
- const { data: tasks, error } = await supabase
49
- .from('roadmap_chunks')
50
- .select('id, title, priority, status, step_number, prompt_content')
51
- .eq('project_id', projectId)
52
- .neq('status', 'COMPLETED')
53
- .order('priority', { ascending: false })
54
- .order('step_number', { ascending: true });
55
-
56
43
  if (error) {
44
+ if (error.message.includes('Access Denied')) {
45
+ throw new Error('Project not found or access denied');
46
+ }
57
47
  console.error('Failed to fetch roadmap tasks:', error);
58
48
  throw new Error('Failed to fetch roadmap tasks');
59
49
  }
60
50
 
51
+ // 2. Filter non-completed in JS (to preserve original logic)
52
+ const activeTasks = (tasks || [])
53
+ .filter((t: any) => t.status !== 'COMPLETED')
54
+ .sort((a: any, b: any) => {
55
+ if (a.priority !== b.priority) {
56
+ const priorityOrder: any = { 'CRITICAL': 3, 'HIGH': 2, 'MEDIUM': 1, 'LOW': 0 };
57
+ return (priorityOrder[b.priority] || 0) - (priorityOrder[a.priority] || 0);
58
+ }
59
+ return a.step_number - b.step_number;
60
+ });
61
+
61
62
  // 3. Format response
62
- const formatted = (tasks || []).length > 0
63
- ? (tasks || []).map(t => {
63
+ const formatted = activeTasks.length > 0
64
+ ? activeTasks.map((t: any) => {
64
65
  const statusEmoji = t.status === 'ACTIVE' ? '🔵' : '🔒';
65
- const priorityStr = t.priority ? `[${t.priority}]` : '';
66
66
  return `${statusEmoji} Step ${t.step_number}: ${t.title} (ID: ${t.id})`;
67
67
  }).join('\n')
68
68
  : 'No active or locked tasks found in the roadmap.';
69
69
 
70
70
  return {
71
- tasks: (tasks || []).map(t => ({
71
+ tasks: activeTasks.map((t: any) => ({
72
72
  id: t.id,
73
73
  title: t.title,
74
74
  priority: t.priority,