@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
|
@@ -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
|
+
}
|