@vibescope/mcp-server 0.0.1
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/README.md +98 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.js +356 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +367 -0
- package/dist/handlers/__test-utils__.d.ts +72 -0
- package/dist/handlers/__test-utils__.js +176 -0
- package/dist/handlers/blockers.d.ts +18 -0
- package/dist/handlers/blockers.js +81 -0
- package/dist/handlers/bodies-of-work.d.ts +34 -0
- package/dist/handlers/bodies-of-work.js +614 -0
- package/dist/handlers/checkouts.d.ts +37 -0
- package/dist/handlers/checkouts.js +377 -0
- package/dist/handlers/cost.d.ts +39 -0
- package/dist/handlers/cost.js +247 -0
- package/dist/handlers/decisions.d.ts +16 -0
- package/dist/handlers/decisions.js +64 -0
- package/dist/handlers/deployment.d.ts +36 -0
- package/dist/handlers/deployment.js +1062 -0
- package/dist/handlers/discovery.d.ts +14 -0
- package/dist/handlers/discovery.js +870 -0
- package/dist/handlers/fallback.d.ts +18 -0
- package/dist/handlers/fallback.js +216 -0
- package/dist/handlers/findings.d.ts +18 -0
- package/dist/handlers/findings.js +110 -0
- package/dist/handlers/git-issues.d.ts +22 -0
- package/dist/handlers/git-issues.js +247 -0
- package/dist/handlers/ideas.d.ts +19 -0
- package/dist/handlers/ideas.js +188 -0
- package/dist/handlers/index.d.ts +29 -0
- package/dist/handlers/index.js +65 -0
- package/dist/handlers/knowledge-query.d.ts +22 -0
- package/dist/handlers/knowledge-query.js +253 -0
- package/dist/handlers/knowledge.d.ts +12 -0
- package/dist/handlers/knowledge.js +108 -0
- package/dist/handlers/milestones.d.ts +20 -0
- package/dist/handlers/milestones.js +179 -0
- package/dist/handlers/organizations.d.ts +36 -0
- package/dist/handlers/organizations.js +428 -0
- package/dist/handlers/progress.d.ts +14 -0
- package/dist/handlers/progress.js +149 -0
- package/dist/handlers/project.d.ts +20 -0
- package/dist/handlers/project.js +278 -0
- package/dist/handlers/requests.d.ts +16 -0
- package/dist/handlers/requests.js +131 -0
- package/dist/handlers/roles.d.ts +30 -0
- package/dist/handlers/roles.js +281 -0
- package/dist/handlers/session.d.ts +20 -0
- package/dist/handlers/session.js +791 -0
- package/dist/handlers/tasks.d.ts +52 -0
- package/dist/handlers/tasks.js +1111 -0
- package/dist/handlers/tasks.test.d.ts +1 -0
- package/dist/handlers/tasks.test.js +431 -0
- package/dist/handlers/types.d.ts +94 -0
- package/dist/handlers/types.js +1 -0
- package/dist/handlers/validation.d.ts +16 -0
- package/dist/handlers/validation.js +188 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2707 -0
- package/dist/knowledge.d.ts +6 -0
- package/dist/knowledge.js +121 -0
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +2498 -0
- package/dist/utils.d.ts +149 -0
- package/dist/utils.js +317 -0
- package/dist/utils.test.d.ts +1 -0
- package/dist/utils.test.js +532 -0
- package/dist/validators.d.ts +35 -0
- package/dist/validators.js +111 -0
- package/dist/validators.test.d.ts +1 -0
- package/dist/validators.test.js +176 -0
- package/package.json +44 -0
- package/src/cli.test.ts +442 -0
- package/src/cli.ts +439 -0
- package/src/handlers/__test-utils__.ts +217 -0
- package/src/handlers/blockers.test.ts +390 -0
- package/src/handlers/blockers.ts +110 -0
- package/src/handlers/bodies-of-work.test.ts +1276 -0
- package/src/handlers/bodies-of-work.ts +783 -0
- package/src/handlers/cost.test.ts +436 -0
- package/src/handlers/cost.ts +322 -0
- package/src/handlers/decisions.test.ts +401 -0
- package/src/handlers/decisions.ts +86 -0
- package/src/handlers/deployment.test.ts +516 -0
- package/src/handlers/deployment.ts +1289 -0
- package/src/handlers/discovery.test.ts +254 -0
- package/src/handlers/discovery.ts +969 -0
- package/src/handlers/fallback.test.ts +687 -0
- package/src/handlers/fallback.ts +260 -0
- package/src/handlers/findings.test.ts +565 -0
- package/src/handlers/findings.ts +153 -0
- package/src/handlers/ideas.test.ts +753 -0
- package/src/handlers/ideas.ts +247 -0
- package/src/handlers/index.ts +69 -0
- package/src/handlers/milestones.test.ts +584 -0
- package/src/handlers/milestones.ts +217 -0
- package/src/handlers/organizations.test.ts +997 -0
- package/src/handlers/organizations.ts +550 -0
- package/src/handlers/progress.test.ts +369 -0
- package/src/handlers/progress.ts +188 -0
- package/src/handlers/project.test.ts +562 -0
- package/src/handlers/project.ts +352 -0
- package/src/handlers/requests.test.ts +531 -0
- package/src/handlers/requests.ts +150 -0
- package/src/handlers/session.test.ts +459 -0
- package/src/handlers/session.ts +912 -0
- package/src/handlers/tasks.test.ts +602 -0
- package/src/handlers/tasks.ts +1393 -0
- package/src/handlers/types.ts +88 -0
- package/src/handlers/validation.test.ts +880 -0
- package/src/handlers/validation.ts +223 -0
- package/src/index.ts +3205 -0
- package/src/knowledge.ts +132 -0
- package/src/tmpclaude-0078-cwd +1 -0
- package/src/tmpclaude-0ee1-cwd +1 -0
- package/src/tmpclaude-2dd5-cwd +1 -0
- package/src/tmpclaude-344c-cwd +1 -0
- package/src/tmpclaude-3860-cwd +1 -0
- package/src/tmpclaude-4b63-cwd +1 -0
- package/src/tmpclaude-5c73-cwd +1 -0
- package/src/tmpclaude-5ee3-cwd +1 -0
- package/src/tmpclaude-6795-cwd +1 -0
- package/src/tmpclaude-709e-cwd +1 -0
- package/src/tmpclaude-9839-cwd +1 -0
- package/src/tmpclaude-d829-cwd +1 -0
- package/src/tmpclaude-e072-cwd +1 -0
- package/src/tmpclaude-f6ee-cwd +1 -0
- package/src/utils.test.ts +681 -0
- package/src/utils.ts +375 -0
- package/src/validators.test.ts +223 -0
- package/src/validators.ts +122 -0
- package/tmpclaude-0439-cwd +1 -0
- package/tmpclaude-132f-cwd +1 -0
- package/tmpclaude-15bb-cwd +1 -0
- package/tmpclaude-165a-cwd +1 -0
- package/tmpclaude-1ba9-cwd +1 -0
- package/tmpclaude-21a3-cwd +1 -0
- package/tmpclaude-2a38-cwd +1 -0
- package/tmpclaude-2adf-cwd +1 -0
- package/tmpclaude-2f56-cwd +1 -0
- package/tmpclaude-3626-cwd +1 -0
- package/tmpclaude-3727-cwd +1 -0
- package/tmpclaude-40bc-cwd +1 -0
- package/tmpclaude-436f-cwd +1 -0
- package/tmpclaude-4783-cwd +1 -0
- package/tmpclaude-4b6d-cwd +1 -0
- package/tmpclaude-4ba4-cwd +1 -0
- package/tmpclaude-51e6-cwd +1 -0
- package/tmpclaude-5ecf-cwd +1 -0
- package/tmpclaude-6f97-cwd +1 -0
- package/tmpclaude-7fb2-cwd +1 -0
- package/tmpclaude-825c-cwd +1 -0
- package/tmpclaude-8baf-cwd +1 -0
- package/tmpclaude-8d9f-cwd +1 -0
- package/tmpclaude-975c-cwd +1 -0
- package/tmpclaude-9983-cwd +1 -0
- package/tmpclaude-a045-cwd +1 -0
- package/tmpclaude-ac4a-cwd +1 -0
- package/tmpclaude-b593-cwd +1 -0
- package/tmpclaude-b891-cwd +1 -0
- package/tmpclaude-c032-cwd +1 -0
- package/tmpclaude-cf43-cwd +1 -0
- package/tmpclaude-d040-cwd +1 -0
- package/tmpclaude-dcdd-cwd +1 -0
- package/tmpclaude-dcee-cwd +1 -0
- package/tmpclaude-e16b-cwd +1 -0
- package/tmpclaude-ecd2-cwd +1 -0
- package/tmpclaude-f48d-cwd +1 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles progress logging and activity feed:
|
|
5
|
+
* - log_progress
|
|
6
|
+
* - get_activity_feed
|
|
7
|
+
*/
|
|
8
|
+
import { validateRequired, validateUUID } from '../validators.js';
|
|
9
|
+
export const logProgress = async (args, ctx) => {
|
|
10
|
+
const { project_id, task_id, summary, details } = args;
|
|
11
|
+
const { supabase, session } = ctx;
|
|
12
|
+
const { error } = await supabase
|
|
13
|
+
.from('progress_logs')
|
|
14
|
+
.insert({
|
|
15
|
+
project_id,
|
|
16
|
+
task_id: task_id || null,
|
|
17
|
+
summary,
|
|
18
|
+
details: details || null,
|
|
19
|
+
created_by: 'agent',
|
|
20
|
+
created_by_session_id: session.currentSessionId,
|
|
21
|
+
});
|
|
22
|
+
if (error)
|
|
23
|
+
throw new Error(`Failed to log progress: ${error.message}`);
|
|
24
|
+
return { result: { success: true } };
|
|
25
|
+
};
|
|
26
|
+
export const getActivityFeed = async (args, ctx) => {
|
|
27
|
+
const { project_id, types, created_by, since, limit = 50 } = args;
|
|
28
|
+
validateRequired(project_id, 'project_id');
|
|
29
|
+
validateUUID(project_id, 'project_id');
|
|
30
|
+
const { supabase } = ctx;
|
|
31
|
+
const effectiveLimit = Math.min(limit, 200);
|
|
32
|
+
const includeTypes = types || ['task', 'progress', 'blocker', 'decision'];
|
|
33
|
+
// Fetch slightly more per type to account for interleaving, but not full limit
|
|
34
|
+
const perTypeLimit = Math.min(Math.ceil(effectiveLimit / includeTypes.length) * 2, effectiveLimit);
|
|
35
|
+
const activities = [];
|
|
36
|
+
// Fetch tasks if included
|
|
37
|
+
if (includeTypes.includes('task')) {
|
|
38
|
+
let query = supabase
|
|
39
|
+
.from('tasks')
|
|
40
|
+
.select('id, title, status, created_by, created_at, completed_at')
|
|
41
|
+
.eq('project_id', project_id)
|
|
42
|
+
.order('created_at', { ascending: false })
|
|
43
|
+
.limit(perTypeLimit);
|
|
44
|
+
if (created_by)
|
|
45
|
+
query = query.eq('created_by', created_by);
|
|
46
|
+
if (since)
|
|
47
|
+
query = query.gt('created_at', since);
|
|
48
|
+
const { data: tasks } = await query;
|
|
49
|
+
if (tasks) {
|
|
50
|
+
for (const task of tasks) {
|
|
51
|
+
activities.push({
|
|
52
|
+
type: 'task',
|
|
53
|
+
id: task.id,
|
|
54
|
+
title: task.title,
|
|
55
|
+
status: task.status,
|
|
56
|
+
created_by: task.created_by,
|
|
57
|
+
created_at: task.created_at,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Fetch progress logs if included
|
|
63
|
+
if (includeTypes.includes('progress')) {
|
|
64
|
+
let query = supabase
|
|
65
|
+
.from('progress_logs')
|
|
66
|
+
.select('id, summary, created_by, created_at')
|
|
67
|
+
.eq('project_id', project_id)
|
|
68
|
+
.order('created_at', { ascending: false })
|
|
69
|
+
.limit(perTypeLimit);
|
|
70
|
+
if (created_by)
|
|
71
|
+
query = query.eq('created_by', created_by);
|
|
72
|
+
if (since)
|
|
73
|
+
query = query.gt('created_at', since);
|
|
74
|
+
const { data: logs } = await query;
|
|
75
|
+
if (logs) {
|
|
76
|
+
for (const log of logs) {
|
|
77
|
+
activities.push({
|
|
78
|
+
type: 'progress',
|
|
79
|
+
id: log.id,
|
|
80
|
+
summary: log.summary,
|
|
81
|
+
created_by: log.created_by,
|
|
82
|
+
created_at: log.created_at,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Fetch blockers if included
|
|
88
|
+
if (includeTypes.includes('blocker')) {
|
|
89
|
+
let query = supabase
|
|
90
|
+
.from('blockers')
|
|
91
|
+
.select('id, description, status, created_by, created_at')
|
|
92
|
+
.eq('project_id', project_id)
|
|
93
|
+
.order('created_at', { ascending: false })
|
|
94
|
+
.limit(perTypeLimit);
|
|
95
|
+
if (created_by)
|
|
96
|
+
query = query.eq('created_by', created_by);
|
|
97
|
+
if (since)
|
|
98
|
+
query = query.gt('created_at', since);
|
|
99
|
+
const { data: blockers } = await query;
|
|
100
|
+
if (blockers) {
|
|
101
|
+
for (const blocker of blockers) {
|
|
102
|
+
activities.push({
|
|
103
|
+
type: 'blocker',
|
|
104
|
+
id: blocker.id,
|
|
105
|
+
description: blocker.description,
|
|
106
|
+
status: blocker.status,
|
|
107
|
+
created_by: blocker.created_by,
|
|
108
|
+
created_at: blocker.created_at,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Fetch decisions if included
|
|
114
|
+
if (includeTypes.includes('decision')) {
|
|
115
|
+
let query = supabase
|
|
116
|
+
.from('decisions')
|
|
117
|
+
.select('id, title, created_by, created_at')
|
|
118
|
+
.eq('project_id', project_id)
|
|
119
|
+
.order('created_at', { ascending: false })
|
|
120
|
+
.limit(perTypeLimit);
|
|
121
|
+
if (created_by)
|
|
122
|
+
query = query.eq('created_by', created_by);
|
|
123
|
+
if (since)
|
|
124
|
+
query = query.gt('created_at', since);
|
|
125
|
+
const { data: decisions } = await query;
|
|
126
|
+
if (decisions) {
|
|
127
|
+
for (const decision of decisions) {
|
|
128
|
+
activities.push({
|
|
129
|
+
type: 'decision',
|
|
130
|
+
id: decision.id,
|
|
131
|
+
title: decision.title,
|
|
132
|
+
created_by: decision.created_by,
|
|
133
|
+
created_at: decision.created_at,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Sort by created_at descending and limit
|
|
139
|
+
activities.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
140
|
+
const limitedActivities = activities.slice(0, effectiveLimit);
|
|
141
|
+
return { result: { activities: limitedActivities } };
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Progress handlers registry
|
|
145
|
+
*/
|
|
146
|
+
export const progressHandlers = {
|
|
147
|
+
log_progress: logProgress,
|
|
148
|
+
get_activity_feed: getActivityFeed,
|
|
149
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles project CRUD and configuration:
|
|
5
|
+
* - get_project_context
|
|
6
|
+
* - get_git_workflow
|
|
7
|
+
* - create_project
|
|
8
|
+
* - update_project
|
|
9
|
+
* - update_project_readme
|
|
10
|
+
*/
|
|
11
|
+
import type { Handler, HandlerRegistry } from './types.js';
|
|
12
|
+
export declare const getProjectContext: Handler;
|
|
13
|
+
export declare const getGitWorkflow: Handler;
|
|
14
|
+
export declare const createProject: Handler;
|
|
15
|
+
export declare const updateProject: Handler;
|
|
16
|
+
export declare const updateProjectReadme: Handler;
|
|
17
|
+
/**
|
|
18
|
+
* Project handlers registry
|
|
19
|
+
*/
|
|
20
|
+
export declare const projectHandlers: HandlerRegistry;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles project CRUD and configuration:
|
|
5
|
+
* - get_project_context
|
|
6
|
+
* - get_git_workflow
|
|
7
|
+
* - create_project
|
|
8
|
+
* - update_project
|
|
9
|
+
* - update_project_readme
|
|
10
|
+
*/
|
|
11
|
+
import { validateRequired, validateUUID, validateProjectStatus } from '../validators.js';
|
|
12
|
+
/**
|
|
13
|
+
* Get user-created items since last sync
|
|
14
|
+
*/
|
|
15
|
+
async function getUserUpdates(supabase, auth, projectId, currentSessionId) {
|
|
16
|
+
let lastSyncedAt;
|
|
17
|
+
if (currentSessionId) {
|
|
18
|
+
const { data: session } = await supabase
|
|
19
|
+
.from('agent_sessions')
|
|
20
|
+
.select('last_synced_at')
|
|
21
|
+
.eq('id', currentSessionId)
|
|
22
|
+
.single();
|
|
23
|
+
lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const { data: session } = await supabase
|
|
27
|
+
.from('agent_sessions')
|
|
28
|
+
.select('last_synced_at')
|
|
29
|
+
.eq('api_key_id', auth.apiKeyId)
|
|
30
|
+
.eq('project_id', projectId)
|
|
31
|
+
.single();
|
|
32
|
+
lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
|
|
33
|
+
}
|
|
34
|
+
const [tasksResult, blockersResult, ideasResult] = await Promise.all([
|
|
35
|
+
supabase
|
|
36
|
+
.from('tasks')
|
|
37
|
+
.select('id, title, created_at')
|
|
38
|
+
.eq('project_id', projectId)
|
|
39
|
+
.eq('created_by', 'user')
|
|
40
|
+
.gt('created_at', lastSyncedAt)
|
|
41
|
+
.order('created_at', { ascending: false })
|
|
42
|
+
.limit(5),
|
|
43
|
+
supabase
|
|
44
|
+
.from('blockers')
|
|
45
|
+
.select('id, description, created_at')
|
|
46
|
+
.eq('project_id', projectId)
|
|
47
|
+
.eq('created_by', 'user')
|
|
48
|
+
.gt('created_at', lastSyncedAt)
|
|
49
|
+
.order('created_at', { ascending: false })
|
|
50
|
+
.limit(5),
|
|
51
|
+
supabase
|
|
52
|
+
.from('ideas')
|
|
53
|
+
.select('id, title, created_at')
|
|
54
|
+
.eq('project_id', projectId)
|
|
55
|
+
.eq('created_by', 'user')
|
|
56
|
+
.gt('created_at', lastSyncedAt)
|
|
57
|
+
.order('created_at', { ascending: false })
|
|
58
|
+
.limit(5),
|
|
59
|
+
]);
|
|
60
|
+
const tasks = tasksResult.data || [];
|
|
61
|
+
const blockers = blockersResult.data || [];
|
|
62
|
+
const ideas = ideasResult.data || [];
|
|
63
|
+
if (tasks.length === 0 && blockers.length === 0 && ideas.length === 0) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return { tasks, blockers, ideas };
|
|
67
|
+
}
|
|
68
|
+
export const getProjectContext = async (args, ctx) => {
|
|
69
|
+
const { project_id, git_url } = args;
|
|
70
|
+
const { supabase, auth, session } = ctx;
|
|
71
|
+
// If no project_id or git_url, list all projects
|
|
72
|
+
if (!project_id && !git_url) {
|
|
73
|
+
const { data: projects, error } = await supabase
|
|
74
|
+
.from('projects')
|
|
75
|
+
.select('id, name, description, status, git_url')
|
|
76
|
+
.eq('user_id', auth.userId)
|
|
77
|
+
.order('updated_at', { ascending: false });
|
|
78
|
+
if (error)
|
|
79
|
+
throw new Error(`Failed to fetch projects: ${error.message}`);
|
|
80
|
+
return { result: { projects: projects || [] } };
|
|
81
|
+
}
|
|
82
|
+
// Find project by ID or git_url
|
|
83
|
+
let query = supabase
|
|
84
|
+
.from('projects')
|
|
85
|
+
.select('id, name, description, goal, status, git_url, agent_instructions, tech_stack')
|
|
86
|
+
.eq('user_id', auth.userId);
|
|
87
|
+
if (project_id) {
|
|
88
|
+
query = query.eq('id', project_id);
|
|
89
|
+
}
|
|
90
|
+
else if (git_url) {
|
|
91
|
+
query = query.eq('git_url', git_url);
|
|
92
|
+
}
|
|
93
|
+
const { data: project, error: projectError } = await query.single();
|
|
94
|
+
if (projectError || !project) {
|
|
95
|
+
return {
|
|
96
|
+
result: {
|
|
97
|
+
found: false,
|
|
98
|
+
message: 'Project not found. Use create_project to create one.',
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Fetch related data with minimal fields
|
|
103
|
+
const [tasksResult, blockersResult, decisionsResult, progressResult] = await Promise.all([
|
|
104
|
+
supabase
|
|
105
|
+
.from('tasks')
|
|
106
|
+
.select('id, title, description, priority, status, estimated_minutes')
|
|
107
|
+
.eq('project_id', project.id)
|
|
108
|
+
.in('status', ['pending', 'in_progress'])
|
|
109
|
+
.order('priority', { ascending: true })
|
|
110
|
+
.limit(10),
|
|
111
|
+
supabase
|
|
112
|
+
.from('blockers')
|
|
113
|
+
.select('id, description')
|
|
114
|
+
.eq('project_id', project.id)
|
|
115
|
+
.eq('status', 'open')
|
|
116
|
+
.limit(5),
|
|
117
|
+
supabase
|
|
118
|
+
.from('decisions')
|
|
119
|
+
.select('title')
|
|
120
|
+
.eq('project_id', project.id)
|
|
121
|
+
.order('created_at', { ascending: false })
|
|
122
|
+
.limit(5),
|
|
123
|
+
supabase
|
|
124
|
+
.from('progress_logs')
|
|
125
|
+
.select('summary')
|
|
126
|
+
.eq('project_id', project.id)
|
|
127
|
+
.order('created_at', { ascending: false })
|
|
128
|
+
.limit(5),
|
|
129
|
+
]);
|
|
130
|
+
const userUpdates = await getUserUpdates(supabase, auth, project.id, session.currentSessionId);
|
|
131
|
+
// Build compact response
|
|
132
|
+
const result = {
|
|
133
|
+
found: true,
|
|
134
|
+
project,
|
|
135
|
+
active_tasks: tasksResult.data || [],
|
|
136
|
+
};
|
|
137
|
+
const blockers = blockersResult.data || [];
|
|
138
|
+
const decisions = decisionsResult.data || [];
|
|
139
|
+
const progress = progressResult.data || [];
|
|
140
|
+
if (blockers.length > 0)
|
|
141
|
+
result.open_blockers = blockers;
|
|
142
|
+
if (decisions.length > 0)
|
|
143
|
+
result.recent_decisions = decisions;
|
|
144
|
+
if (progress.length > 0)
|
|
145
|
+
result.recent_progress = progress;
|
|
146
|
+
return { result, user_updates: userUpdates };
|
|
147
|
+
};
|
|
148
|
+
export const getGitWorkflow = async (args, ctx) => {
|
|
149
|
+
const { project_id, task_id } = args;
|
|
150
|
+
validateRequired(project_id, 'project_id');
|
|
151
|
+
validateUUID(project_id, 'project_id');
|
|
152
|
+
const { supabase } = ctx;
|
|
153
|
+
const { data: project, error } = await supabase
|
|
154
|
+
.from('projects')
|
|
155
|
+
.select('git_workflow, git_main_branch, git_develop_branch, git_auto_branch, git_auto_tag, git_url')
|
|
156
|
+
.eq('id', project_id)
|
|
157
|
+
.single();
|
|
158
|
+
if (error || !project)
|
|
159
|
+
throw new Error(`Project not found: ${project_id}`);
|
|
160
|
+
let task = null;
|
|
161
|
+
if (task_id) {
|
|
162
|
+
const { data: taskData } = await supabase
|
|
163
|
+
.from('tasks')
|
|
164
|
+
.select('id, title, git_branch')
|
|
165
|
+
.eq('id', task_id)
|
|
166
|
+
.single();
|
|
167
|
+
task = taskData;
|
|
168
|
+
}
|
|
169
|
+
// Generate workflow instructions
|
|
170
|
+
const workflow = project.git_workflow || 'none';
|
|
171
|
+
const mainBranch = project.git_main_branch || 'main';
|
|
172
|
+
const developBranch = project.git_develop_branch || 'develop';
|
|
173
|
+
const instructions = {
|
|
174
|
+
'none': [
|
|
175
|
+
'No git workflow configured for this project.',
|
|
176
|
+
'Commit changes directly without branching strategy.',
|
|
177
|
+
],
|
|
178
|
+
'trunk-based': [
|
|
179
|
+
`Work directly on the ${mainBranch} branch.`,
|
|
180
|
+
'Make small, frequent commits.',
|
|
181
|
+
'Ensure all tests pass before committing.',
|
|
182
|
+
`Tag deployments on ${mainBranch} (e.g., git tag v1.0.0).`,
|
|
183
|
+
],
|
|
184
|
+
'github-flow': [
|
|
185
|
+
task ? `Create feature branch: git checkout -b feature/${task.id.slice(0, 8)}-${task.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 30)}` : 'Create a feature branch for each task.',
|
|
186
|
+
'Make commits with descriptive messages.',
|
|
187
|
+
'Push branch and create a pull request.',
|
|
188
|
+
`Merge to ${mainBranch} after review/validation.`,
|
|
189
|
+
project.git_auto_tag ? 'Deployments will be automatically tagged.' : `Tag deployments manually on ${mainBranch}.`,
|
|
190
|
+
],
|
|
191
|
+
'git-flow': [
|
|
192
|
+
task ? `Create feature branch from ${developBranch}: git checkout -b feature/${task.id.slice(0, 8)}-${task.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 30)} ${developBranch}` : `Create feature branches from ${developBranch}.`,
|
|
193
|
+
`Merge completed features back to ${developBranch}.`,
|
|
194
|
+
`Create release branches from ${developBranch} when ready.`,
|
|
195
|
+
`Merge releases to both ${mainBranch} and ${developBranch}.`,
|
|
196
|
+
`Tag releases on ${mainBranch}.`,
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
const result = {
|
|
200
|
+
workflow,
|
|
201
|
+
main_branch: mainBranch,
|
|
202
|
+
develop_branch: workflow === 'git-flow' ? developBranch : null,
|
|
203
|
+
auto_branch: project.git_auto_branch,
|
|
204
|
+
auto_tag: project.git_auto_tag,
|
|
205
|
+
instructions: instructions[workflow] || instructions['none'],
|
|
206
|
+
};
|
|
207
|
+
if (task) {
|
|
208
|
+
result.task = {
|
|
209
|
+
id: task.id,
|
|
210
|
+
title: task.title,
|
|
211
|
+
current_branch: task.git_branch,
|
|
212
|
+
suggested_branch: workflow !== 'none' && workflow !== 'trunk-based'
|
|
213
|
+
? `feature/${task.id.slice(0, 8)}-${task.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 30)}`
|
|
214
|
+
: null,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return { result };
|
|
218
|
+
};
|
|
219
|
+
export const createProject = async (args, ctx) => {
|
|
220
|
+
const { name, description, goal, git_url, tech_stack } = args;
|
|
221
|
+
const { supabase, auth } = ctx;
|
|
222
|
+
const { data, error } = await supabase
|
|
223
|
+
.from('projects')
|
|
224
|
+
.insert({
|
|
225
|
+
user_id: auth.userId,
|
|
226
|
+
name,
|
|
227
|
+
description: description || null,
|
|
228
|
+
goal: goal || null,
|
|
229
|
+
git_url: git_url || null,
|
|
230
|
+
tech_stack: tech_stack || null,
|
|
231
|
+
})
|
|
232
|
+
.select()
|
|
233
|
+
.single();
|
|
234
|
+
if (error)
|
|
235
|
+
throw new Error(`Failed to create project: ${error.message}`);
|
|
236
|
+
return { result: { success: true, project: data } };
|
|
237
|
+
};
|
|
238
|
+
export const updateProject = async (args, ctx) => {
|
|
239
|
+
const { project_id, ...updates } = args;
|
|
240
|
+
validateRequired(project_id, 'project_id');
|
|
241
|
+
validateUUID(project_id, 'project_id');
|
|
242
|
+
validateProjectStatus(updates.status);
|
|
243
|
+
const { supabase, auth } = ctx;
|
|
244
|
+
const { error } = await supabase
|
|
245
|
+
.from('projects')
|
|
246
|
+
.update(updates)
|
|
247
|
+
.eq('id', project_id)
|
|
248
|
+
.eq('user_id', auth.userId);
|
|
249
|
+
if (error)
|
|
250
|
+
throw new Error(`Failed to update project: ${error.message}`);
|
|
251
|
+
return { result: { success: true, project_id } };
|
|
252
|
+
};
|
|
253
|
+
export const updateProjectReadme = async (args, ctx) => {
|
|
254
|
+
const { project_id, readme_content } = args;
|
|
255
|
+
validateRequired(project_id, 'project_id');
|
|
256
|
+
validateUUID(project_id, 'project_id');
|
|
257
|
+
validateRequired(readme_content, 'readme_content');
|
|
258
|
+
const { error } = await ctx.supabase
|
|
259
|
+
.from('projects')
|
|
260
|
+
.update({
|
|
261
|
+
readme_content,
|
|
262
|
+
readme_updated_at: new Date().toISOString(),
|
|
263
|
+
})
|
|
264
|
+
.eq('id', project_id);
|
|
265
|
+
if (error)
|
|
266
|
+
throw new Error(`Failed to update README: ${error.message}`);
|
|
267
|
+
return { result: { success: true } };
|
|
268
|
+
};
|
|
269
|
+
/**
|
|
270
|
+
* Project handlers registry
|
|
271
|
+
*/
|
|
272
|
+
export const projectHandlers = {
|
|
273
|
+
get_project_context: getProjectContext,
|
|
274
|
+
get_git_workflow: getGitWorkflow,
|
|
275
|
+
create_project: createProject,
|
|
276
|
+
update_project: updateProject,
|
|
277
|
+
update_project_readme: updateProjectReadme,
|
|
278
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requests Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles user request handling:
|
|
5
|
+
* - get_pending_requests
|
|
6
|
+
* - acknowledge_request
|
|
7
|
+
* - answer_question
|
|
8
|
+
*/
|
|
9
|
+
import type { Handler, HandlerRegistry } from './types.js';
|
|
10
|
+
export declare const getPendingRequests: Handler;
|
|
11
|
+
export declare const acknowledgeRequest: Handler;
|
|
12
|
+
export declare const answerQuestion: Handler;
|
|
13
|
+
/**
|
|
14
|
+
* Requests handlers registry
|
|
15
|
+
*/
|
|
16
|
+
export declare const requestHandlers: HandlerRegistry;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requests Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles user request handling:
|
|
5
|
+
* - get_pending_requests
|
|
6
|
+
* - acknowledge_request
|
|
7
|
+
* - answer_question
|
|
8
|
+
*/
|
|
9
|
+
import { validateRequired, validateUUID } from '../validators.js';
|
|
10
|
+
export const getPendingRequests = async (args, ctx) => {
|
|
11
|
+
const { project_id } = args;
|
|
12
|
+
validateRequired(project_id, 'project_id');
|
|
13
|
+
validateUUID(project_id, 'project_id');
|
|
14
|
+
const { supabase, session } = ctx;
|
|
15
|
+
const currentSessionId = session.currentSessionId;
|
|
16
|
+
// Get active session IDs to identify orphaned questions
|
|
17
|
+
const { data: activeSessions } = await supabase
|
|
18
|
+
.from('agent_sessions')
|
|
19
|
+
.select('id')
|
|
20
|
+
.eq('project_id', project_id)
|
|
21
|
+
.eq('status', 'active');
|
|
22
|
+
const activeSessionIds = new Set((activeSessions || []).map((s) => s.id));
|
|
23
|
+
// Get pending requests for this project:
|
|
24
|
+
// - Unacknowledged requests OR unanswered questions (questions need answers, not just acknowledgment)
|
|
25
|
+
const { data: requests, error } = await supabase
|
|
26
|
+
.from('agent_requests')
|
|
27
|
+
.select('*')
|
|
28
|
+
.eq('project_id', project_id)
|
|
29
|
+
.or('acknowledged_at.is.null,and(request_type.eq.question,answered_at.is.null)')
|
|
30
|
+
.order('created_at', { ascending: false });
|
|
31
|
+
if (error)
|
|
32
|
+
throw error;
|
|
33
|
+
// Filter to requests this agent can handle
|
|
34
|
+
const filteredRequests = (requests || []).filter((r) => {
|
|
35
|
+
// Broadcast requests (session_id is null) - anyone can handle
|
|
36
|
+
if (!r.session_id)
|
|
37
|
+
return true;
|
|
38
|
+
// Targeted to this session
|
|
39
|
+
if (r.session_id === currentSessionId)
|
|
40
|
+
return true;
|
|
41
|
+
// Orphaned questions (targeted session is disconnected) - any agent can answer
|
|
42
|
+
if (r.request_type === 'question' && !activeSessionIds.has(r.session_id))
|
|
43
|
+
return true;
|
|
44
|
+
return false;
|
|
45
|
+
});
|
|
46
|
+
// Sort questions first (highest priority) and add wait times
|
|
47
|
+
const now = new Date();
|
|
48
|
+
const sortedRequests = filteredRequests
|
|
49
|
+
.map((r) => ({
|
|
50
|
+
...r,
|
|
51
|
+
wait_minutes: Math.floor((now.getTime() - new Date(r.created_at).getTime()) / 60000),
|
|
52
|
+
}))
|
|
53
|
+
.sort((a, b) => {
|
|
54
|
+
// Questions first, then by created_at (oldest first for urgency)
|
|
55
|
+
const aIsQuestion = a.request_type === 'question' && !a.answered_at;
|
|
56
|
+
const bIsQuestion = b.request_type === 'question' && !b.answered_at;
|
|
57
|
+
if (aIsQuestion && !bIsQuestion)
|
|
58
|
+
return -1;
|
|
59
|
+
if (!aIsQuestion && bIsQuestion)
|
|
60
|
+
return 1;
|
|
61
|
+
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
|
|
62
|
+
});
|
|
63
|
+
// Count unanswered questions separately
|
|
64
|
+
const questionsCount = sortedRequests.filter((r) => r.request_type === 'question' && !r.answered_at).length;
|
|
65
|
+
return {
|
|
66
|
+
result: {
|
|
67
|
+
requests: sortedRequests,
|
|
68
|
+
count: sortedRequests.length,
|
|
69
|
+
questions_count: questionsCount,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
export const acknowledgeRequest = async (args, ctx) => {
|
|
74
|
+
const { request_id } = args;
|
|
75
|
+
validateRequired(request_id, 'request_id');
|
|
76
|
+
validateUUID(request_id, 'request_id');
|
|
77
|
+
const { supabase, session } = ctx;
|
|
78
|
+
const { data: request, error } = await supabase
|
|
79
|
+
.from('agent_requests')
|
|
80
|
+
.update({
|
|
81
|
+
acknowledged_at: new Date().toISOString(),
|
|
82
|
+
acknowledged_by_session_id: session.currentSessionId,
|
|
83
|
+
})
|
|
84
|
+
.eq('id', request_id)
|
|
85
|
+
.select()
|
|
86
|
+
.single();
|
|
87
|
+
if (error)
|
|
88
|
+
throw error;
|
|
89
|
+
return {
|
|
90
|
+
result: {
|
|
91
|
+
success: true,
|
|
92
|
+
request,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
export const answerQuestion = async (args, ctx) => {
|
|
97
|
+
const { request_id, answer } = args;
|
|
98
|
+
validateRequired(request_id, 'request_id');
|
|
99
|
+
validateRequired(answer, 'answer');
|
|
100
|
+
validateUUID(request_id, 'request_id');
|
|
101
|
+
const { supabase, session } = ctx;
|
|
102
|
+
// Update the request with the answer
|
|
103
|
+
const { data: request, error } = await supabase
|
|
104
|
+
.from('agent_requests')
|
|
105
|
+
.update({
|
|
106
|
+
answer,
|
|
107
|
+
answered_at: new Date().toISOString(),
|
|
108
|
+
acknowledged_at: new Date().toISOString(),
|
|
109
|
+
acknowledged_by_session_id: session.currentSessionId,
|
|
110
|
+
})
|
|
111
|
+
.eq('id', request_id)
|
|
112
|
+
.select()
|
|
113
|
+
.single();
|
|
114
|
+
if (error)
|
|
115
|
+
throw error;
|
|
116
|
+
return {
|
|
117
|
+
result: {
|
|
118
|
+
success: true,
|
|
119
|
+
message: 'Question answered successfully',
|
|
120
|
+
request,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Requests handlers registry
|
|
126
|
+
*/
|
|
127
|
+
export const requestHandlers = {
|
|
128
|
+
get_pending_requests: getPendingRequests,
|
|
129
|
+
acknowledge_request: acknowledgeRequest,
|
|
130
|
+
answer_question: answerQuestion,
|
|
131
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role Handlers
|
|
3
|
+
*
|
|
4
|
+
* Manages agent roles for specialized work:
|
|
5
|
+
* - get_role_settings: Get role configuration for a project
|
|
6
|
+
* - update_role_settings: Update role settings
|
|
7
|
+
* - set_session_role: Change the current session's role
|
|
8
|
+
* - get_agents_by_role: Get active agents grouped by role
|
|
9
|
+
*/
|
|
10
|
+
import type { Handler, HandlerRegistry } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Get role settings for a project
|
|
13
|
+
*/
|
|
14
|
+
export declare const getRoleSettings: Handler;
|
|
15
|
+
/**
|
|
16
|
+
* Update role settings for a project
|
|
17
|
+
*/
|
|
18
|
+
export declare const updateRoleSettings: Handler;
|
|
19
|
+
/**
|
|
20
|
+
* Set the current session's role
|
|
21
|
+
*/
|
|
22
|
+
export declare const setSessionRole: Handler;
|
|
23
|
+
/**
|
|
24
|
+
* Get active agents grouped by role
|
|
25
|
+
*/
|
|
26
|
+
export declare const getAgentsByRole: Handler;
|
|
27
|
+
/**
|
|
28
|
+
* Role handlers registry
|
|
29
|
+
*/
|
|
30
|
+
export declare const roleHandlers: HandlerRegistry;
|