@vibescope/mcp-server 0.0.1 → 0.2.0
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 +113 -98
- package/dist/api-client.d.ts +1169 -0
- package/dist/api-client.js +713 -0
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +39 -240
- package/dist/config/tool-categories.d.ts +31 -0
- package/dist/config/tool-categories.js +253 -0
- package/dist/handlers/blockers.js +57 -58
- package/dist/handlers/bodies-of-work.d.ts +2 -0
- package/dist/handlers/bodies-of-work.js +108 -477
- package/dist/handlers/cost.d.ts +1 -0
- package/dist/handlers/cost.js +35 -113
- package/dist/handlers/decisions.d.ts +2 -0
- package/dist/handlers/decisions.js +28 -27
- package/dist/handlers/deployment.js +113 -828
- package/dist/handlers/discovery.d.ts +3 -0
- package/dist/handlers/discovery.js +26 -627
- package/dist/handlers/fallback.d.ts +2 -0
- package/dist/handlers/fallback.js +56 -142
- package/dist/handlers/findings.d.ts +8 -1
- package/dist/handlers/findings.js +65 -68
- package/dist/handlers/git-issues.d.ts +9 -13
- package/dist/handlers/git-issues.js +80 -225
- package/dist/handlers/ideas.d.ts +3 -0
- package/dist/handlers/ideas.js +53 -134
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/milestones.d.ts +2 -0
- package/dist/handlers/milestones.js +51 -98
- package/dist/handlers/organizations.js +79 -275
- package/dist/handlers/progress.d.ts +2 -0
- package/dist/handlers/progress.js +25 -123
- package/dist/handlers/project.js +42 -221
- package/dist/handlers/requests.d.ts +2 -0
- package/dist/handlers/requests.js +23 -83
- package/dist/handlers/session.js +119 -590
- package/dist/handlers/sprints.d.ts +32 -0
- package/dist/handlers/sprints.js +275 -0
- package/dist/handlers/tasks.d.ts +7 -10
- package/dist/handlers/tasks.js +245 -894
- package/dist/handlers/tool-docs.d.ts +9 -0
- package/dist/handlers/tool-docs.js +904 -0
- package/dist/handlers/types.d.ts +11 -3
- package/dist/handlers/validation.d.ts +1 -1
- package/dist/handlers/validation.js +38 -153
- package/dist/index.js +493 -162
- package/dist/knowledge.js +106 -9
- package/dist/tools.js +34 -4
- package/dist/validators.d.ts +21 -0
- package/dist/validators.js +91 -0
- package/package.json +2 -3
- package/src/api-client.ts +1822 -0
- package/src/cli.test.ts +128 -302
- package/src/cli.ts +41 -285
- package/src/handlers/__test-setup__.ts +215 -0
- package/src/handlers/__test-utils__.ts +4 -134
- package/src/handlers/blockers.test.ts +114 -124
- package/src/handlers/blockers.ts +68 -70
- package/src/handlers/bodies-of-work.test.ts +236 -831
- package/src/handlers/bodies-of-work.ts +210 -525
- package/src/handlers/cost.test.ts +149 -113
- package/src/handlers/cost.ts +44 -132
- package/src/handlers/decisions.test.ts +111 -209
- package/src/handlers/decisions.ts +35 -27
- package/src/handlers/deployment.test.ts +193 -239
- package/src/handlers/deployment.ts +143 -896
- package/src/handlers/discovery.test.ts +20 -67
- package/src/handlers/discovery.ts +29 -714
- package/src/handlers/fallback.test.ts +206 -361
- package/src/handlers/fallback.ts +81 -156
- package/src/handlers/findings.test.ts +229 -320
- package/src/handlers/findings.ts +76 -64
- package/src/handlers/git-issues.test.ts +623 -0
- package/src/handlers/git-issues.ts +174 -0
- package/src/handlers/ideas.test.ts +229 -343
- package/src/handlers/ideas.ts +69 -143
- package/src/handlers/index.ts +6 -0
- package/src/handlers/milestones.test.ts +167 -281
- package/src/handlers/milestones.ts +54 -93
- package/src/handlers/organizations.test.ts +275 -467
- package/src/handlers/organizations.ts +84 -294
- package/src/handlers/progress.test.ts +112 -218
- package/src/handlers/progress.ts +29 -142
- package/src/handlers/project.test.ts +203 -226
- package/src/handlers/project.ts +48 -238
- package/src/handlers/requests.test.ts +74 -342
- package/src/handlers/requests.ts +25 -83
- package/src/handlers/session.test.ts +276 -206
- package/src/handlers/session.ts +136 -662
- package/src/handlers/sprints.test.ts +711 -0
- package/src/handlers/sprints.ts +510 -0
- package/src/handlers/tasks.test.ts +669 -353
- package/src/handlers/tasks.ts +263 -1015
- package/src/handlers/tool-docs.ts +1024 -0
- package/src/handlers/types.ts +12 -4
- package/src/handlers/validation.test.ts +237 -568
- package/src/handlers/validation.ts +43 -167
- package/src/index.ts +493 -186
- package/src/tools.ts +2532 -0
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +127 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +14 -13
- package/dist/cli.test.d.ts +0 -1
- package/dist/cli.test.js +0 -367
- package/dist/handlers/__test-utils__.d.ts +0 -72
- package/dist/handlers/__test-utils__.js +0 -176
- package/dist/handlers/checkouts.d.ts +0 -37
- package/dist/handlers/checkouts.js +0 -377
- package/dist/handlers/knowledge-query.d.ts +0 -22
- package/dist/handlers/knowledge-query.js +0 -253
- package/dist/handlers/knowledge.d.ts +0 -12
- package/dist/handlers/knowledge.js +0 -108
- package/dist/handlers/roles.d.ts +0 -30
- package/dist/handlers/roles.js +0 -281
- package/dist/handlers/tasks.test.d.ts +0 -1
- package/dist/handlers/tasks.test.js +0 -431
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -532
- package/dist/validators.test.d.ts +0 -1
- package/dist/validators.test.js +0 -176
- package/src/knowledge.ts +0 -132
- package/src/tmpclaude-0078-cwd +0 -1
- package/src/tmpclaude-0ee1-cwd +0 -1
- package/src/tmpclaude-2dd5-cwd +0 -1
- package/src/tmpclaude-344c-cwd +0 -1
- package/src/tmpclaude-3860-cwd +0 -1
- package/src/tmpclaude-4b63-cwd +0 -1
- package/src/tmpclaude-5c73-cwd +0 -1
- package/src/tmpclaude-5ee3-cwd +0 -1
- package/src/tmpclaude-6795-cwd +0 -1
- package/src/tmpclaude-709e-cwd +0 -1
- package/src/tmpclaude-9839-cwd +0 -1
- package/src/tmpclaude-d829-cwd +0 -1
- package/src/tmpclaude-e072-cwd +0 -1
- package/src/tmpclaude-f6ee-cwd +0 -1
- package/tmpclaude-0439-cwd +0 -1
- package/tmpclaude-132f-cwd +0 -1
- package/tmpclaude-15bb-cwd +0 -1
- package/tmpclaude-165a-cwd +0 -1
- package/tmpclaude-1ba9-cwd +0 -1
- package/tmpclaude-21a3-cwd +0 -1
- package/tmpclaude-2a38-cwd +0 -1
- package/tmpclaude-2adf-cwd +0 -1
- package/tmpclaude-2f56-cwd +0 -1
- package/tmpclaude-3626-cwd +0 -1
- package/tmpclaude-3727-cwd +0 -1
- package/tmpclaude-40bc-cwd +0 -1
- package/tmpclaude-436f-cwd +0 -1
- package/tmpclaude-4783-cwd +0 -1
- package/tmpclaude-4b6d-cwd +0 -1
- package/tmpclaude-4ba4-cwd +0 -1
- package/tmpclaude-51e6-cwd +0 -1
- package/tmpclaude-5ecf-cwd +0 -1
- package/tmpclaude-6f97-cwd +0 -1
- package/tmpclaude-7fb2-cwd +0 -1
- package/tmpclaude-825c-cwd +0 -1
- package/tmpclaude-8baf-cwd +0 -1
- package/tmpclaude-8d9f-cwd +0 -1
- package/tmpclaude-975c-cwd +0 -1
- package/tmpclaude-9983-cwd +0 -1
- package/tmpclaude-a045-cwd +0 -1
- package/tmpclaude-ac4a-cwd +0 -1
- package/tmpclaude-b593-cwd +0 -1
- package/tmpclaude-b891-cwd +0 -1
- package/tmpclaude-c032-cwd +0 -1
- package/tmpclaude-cf43-cwd +0 -1
- package/tmpclaude-d040-cwd +0 -1
- package/tmpclaude-dcdd-cwd +0 -1
- package/tmpclaude-dcee-cwd +0 -1
- package/tmpclaude-e16b-cwd +0 -1
- package/tmpclaude-ecd2-cwd +0 -1
- package/tmpclaude-f48d-cwd +0 -1
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File Checkout Handlers
|
|
3
|
-
*
|
|
4
|
-
* Handles file checkout/checkin for multi-agent coordination:
|
|
5
|
-
* - checkout_file: Reserve a file for editing
|
|
6
|
-
* - checkin_file: Release a file after editing
|
|
7
|
-
* - get_file_checkouts: List active and recent checkouts
|
|
8
|
-
* - abandon_checkout: Force-release a checkout
|
|
9
|
-
*/
|
|
10
|
-
import { validateRequired, validateUUID } from '../validators.js';
|
|
11
|
-
/**
|
|
12
|
-
* Verify the user owns or has access to the project
|
|
13
|
-
* This is needed because MCP server uses service_role which bypasses RLS
|
|
14
|
-
*/
|
|
15
|
-
async function verifyProjectAccess(ctx, projectId) {
|
|
16
|
-
const { supabase, auth } = ctx;
|
|
17
|
-
// Check if user owns the project
|
|
18
|
-
const { data: ownedProject } = await supabase
|
|
19
|
-
.from('projects')
|
|
20
|
-
.select('id')
|
|
21
|
-
.eq('id', projectId)
|
|
22
|
-
.eq('user_id', auth.userId)
|
|
23
|
-
.single();
|
|
24
|
-
if (ownedProject)
|
|
25
|
-
return;
|
|
26
|
-
// Check if project is shared with user's organization (for org-scoped keys)
|
|
27
|
-
if (auth.scope === 'organization' && auth.organizationId) {
|
|
28
|
-
const { data: sharedProject } = await supabase
|
|
29
|
-
.from('project_shares')
|
|
30
|
-
.select('project_id')
|
|
31
|
-
.eq('project_id', projectId)
|
|
32
|
-
.eq('organization_id', auth.organizationId)
|
|
33
|
-
.single();
|
|
34
|
-
if (sharedProject)
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
throw new Error('Project not found or access denied');
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Checkout a file for editing
|
|
41
|
-
* Prevents other agents from editing the same file
|
|
42
|
-
*/
|
|
43
|
-
export const checkoutFile = async (args, ctx) => {
|
|
44
|
-
const { project_id, file_path, reason } = args;
|
|
45
|
-
validateRequired(project_id, 'project_id');
|
|
46
|
-
validateUUID(project_id, 'project_id');
|
|
47
|
-
validateRequired(file_path, 'file_path');
|
|
48
|
-
// Verify user has access to this project
|
|
49
|
-
await verifyProjectAccess(ctx, project_id);
|
|
50
|
-
const { supabase, session } = ctx;
|
|
51
|
-
// Check if file is already checked out
|
|
52
|
-
const { data: existing } = await supabase
|
|
53
|
-
.from('file_checkouts')
|
|
54
|
-
.select('id, checked_out_by_session_id, checked_out_at')
|
|
55
|
-
.eq('project_id', project_id)
|
|
56
|
-
.eq('file_path', file_path)
|
|
57
|
-
.eq('status', 'checked_out')
|
|
58
|
-
.single();
|
|
59
|
-
if (existing) {
|
|
60
|
-
// Get session info for better error message
|
|
61
|
-
const { data: sessionInfo } = await supabase
|
|
62
|
-
.from('agent_sessions')
|
|
63
|
-
.select('persona, instance_id')
|
|
64
|
-
.eq('id', existing.checked_out_by_session_id)
|
|
65
|
-
.single();
|
|
66
|
-
const checkedOutBy = sessionInfo?.persona || sessionInfo?.instance_id?.slice(0, 8) || 'another agent';
|
|
67
|
-
const minutesAgo = Math.round((Date.now() - new Date(existing.checked_out_at).getTime()) / 60000);
|
|
68
|
-
return {
|
|
69
|
-
result: {
|
|
70
|
-
success: false,
|
|
71
|
-
error: 'File already checked out',
|
|
72
|
-
checked_out_by: checkedOutBy,
|
|
73
|
-
checked_out_minutes_ago: minutesAgo,
|
|
74
|
-
checkout_id: existing.id,
|
|
75
|
-
hint: 'Wait for the file to be checked in, or use abandon_checkout if the session is stale',
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
// Create the checkout
|
|
80
|
-
const { data, error } = await supabase
|
|
81
|
-
.from('file_checkouts')
|
|
82
|
-
.insert({
|
|
83
|
-
project_id,
|
|
84
|
-
file_path,
|
|
85
|
-
checked_out_by_session_id: session.currentSessionId,
|
|
86
|
-
checkout_reason: reason || null,
|
|
87
|
-
})
|
|
88
|
-
.select('id')
|
|
89
|
-
.single();
|
|
90
|
-
if (error) {
|
|
91
|
-
// Handle unique constraint violation (race condition)
|
|
92
|
-
if (error.code === '23505') {
|
|
93
|
-
return {
|
|
94
|
-
result: {
|
|
95
|
-
success: false,
|
|
96
|
-
error: 'File was just checked out by another agent',
|
|
97
|
-
hint: 'Retry after a moment or choose a different file',
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
throw new Error(`Failed to checkout file: ${error.message}`);
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
result: {
|
|
105
|
-
success: true,
|
|
106
|
-
checkout_id: data.id,
|
|
107
|
-
file_path,
|
|
108
|
-
message: `File checked out successfully. Remember to checkin when done.`,
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
};
|
|
112
|
-
/**
|
|
113
|
-
* Checkin a file after editing
|
|
114
|
-
* Releases the file for other agents to edit
|
|
115
|
-
*/
|
|
116
|
-
export const checkinFile = async (args, ctx) => {
|
|
117
|
-
const { checkout_id, file_path, project_id, summary } = args;
|
|
118
|
-
const { supabase, session } = ctx;
|
|
119
|
-
// Allow checkin by checkout_id OR by file_path + project_id
|
|
120
|
-
let checkoutId = checkout_id;
|
|
121
|
-
let verifiedProjectId;
|
|
122
|
-
if (!checkoutId) {
|
|
123
|
-
if (!file_path || !project_id) {
|
|
124
|
-
throw new Error('Either checkout_id or both file_path and project_id are required');
|
|
125
|
-
}
|
|
126
|
-
validateUUID(project_id, 'project_id');
|
|
127
|
-
// Verify user has access to this project
|
|
128
|
-
await verifyProjectAccess(ctx, project_id);
|
|
129
|
-
verifiedProjectId = project_id;
|
|
130
|
-
// Find the checkout by file path
|
|
131
|
-
const { data: checkout, error: findError } = await supabase
|
|
132
|
-
.from('file_checkouts')
|
|
133
|
-
.select('id')
|
|
134
|
-
.eq('project_id', project_id)
|
|
135
|
-
.eq('file_path', file_path)
|
|
136
|
-
.eq('status', 'checked_out')
|
|
137
|
-
.single();
|
|
138
|
-
if (findError || !checkout) {
|
|
139
|
-
return {
|
|
140
|
-
result: {
|
|
141
|
-
success: false,
|
|
142
|
-
error: 'No active checkout found for this file',
|
|
143
|
-
hint: 'The file may not be checked out or was already checked in',
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
checkoutId = checkout.id;
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
validateUUID(checkoutId, 'checkout_id');
|
|
151
|
-
// Get checkout's project_id to verify access
|
|
152
|
-
const { data: checkout } = await supabase
|
|
153
|
-
.from('file_checkouts')
|
|
154
|
-
.select('project_id')
|
|
155
|
-
.eq('id', checkoutId)
|
|
156
|
-
.single();
|
|
157
|
-
if (!checkout) {
|
|
158
|
-
return {
|
|
159
|
-
result: {
|
|
160
|
-
success: false,
|
|
161
|
-
error: 'Checkout not found',
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
// Verify user has access to this project
|
|
166
|
-
await verifyProjectAccess(ctx, checkout.project_id);
|
|
167
|
-
verifiedProjectId = checkout.project_id;
|
|
168
|
-
}
|
|
169
|
-
// Perform the checkin
|
|
170
|
-
const { data, error } = await supabase
|
|
171
|
-
.from('file_checkouts')
|
|
172
|
-
.update({
|
|
173
|
-
status: 'checked_in',
|
|
174
|
-
checked_in_at: new Date().toISOString(),
|
|
175
|
-
checked_in_by_session_id: session.currentSessionId,
|
|
176
|
-
checkin_summary: summary || null,
|
|
177
|
-
updated_at: new Date().toISOString(),
|
|
178
|
-
})
|
|
179
|
-
.eq('id', checkoutId)
|
|
180
|
-
.eq('status', 'checked_out')
|
|
181
|
-
.select('file_path')
|
|
182
|
-
.single();
|
|
183
|
-
if (error || !data) {
|
|
184
|
-
return {
|
|
185
|
-
result: {
|
|
186
|
-
success: false,
|
|
187
|
-
error: 'Failed to checkin file',
|
|
188
|
-
hint: 'The checkout may have already been checked in or abandoned',
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
result: {
|
|
194
|
-
success: true,
|
|
195
|
-
checkout_id: checkoutId,
|
|
196
|
-
file_path: data.file_path,
|
|
197
|
-
message: 'File checked in successfully',
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
};
|
|
201
|
-
/**
|
|
202
|
-
* Get file checkouts for a project
|
|
203
|
-
*/
|
|
204
|
-
export const getFileCheckouts = async (args, ctx) => {
|
|
205
|
-
const { project_id, status, include_completed = false, limit = 50 } = args;
|
|
206
|
-
validateRequired(project_id, 'project_id');
|
|
207
|
-
validateUUID(project_id, 'project_id');
|
|
208
|
-
// Verify user has access to this project
|
|
209
|
-
await verifyProjectAccess(ctx, project_id);
|
|
210
|
-
const { supabase } = ctx;
|
|
211
|
-
let query = supabase
|
|
212
|
-
.from('file_checkouts')
|
|
213
|
-
.select(`
|
|
214
|
-
id,
|
|
215
|
-
file_path,
|
|
216
|
-
status,
|
|
217
|
-
checkout_reason,
|
|
218
|
-
checkin_summary,
|
|
219
|
-
checked_out_at,
|
|
220
|
-
checked_in_at,
|
|
221
|
-
checked_out_by_session_id,
|
|
222
|
-
checked_in_by_session_id
|
|
223
|
-
`)
|
|
224
|
-
.eq('project_id', project_id)
|
|
225
|
-
.order('checked_out_at', { ascending: false })
|
|
226
|
-
.limit(limit);
|
|
227
|
-
if (status) {
|
|
228
|
-
query = query.eq('status', status);
|
|
229
|
-
}
|
|
230
|
-
else if (!include_completed) {
|
|
231
|
-
query = query.eq('status', 'checked_out');
|
|
232
|
-
}
|
|
233
|
-
const { data, error } = await query;
|
|
234
|
-
if (error)
|
|
235
|
-
throw new Error(`Failed to fetch checkouts: ${error.message}`);
|
|
236
|
-
// Get session info for active checkouts
|
|
237
|
-
const sessionIds = [...new Set((data || [])
|
|
238
|
-
.map(c => c.checked_out_by_session_id)
|
|
239
|
-
.filter(Boolean))];
|
|
240
|
-
let sessionMap = {};
|
|
241
|
-
if (sessionIds.length > 0) {
|
|
242
|
-
const { data: sessions } = await supabase
|
|
243
|
-
.from('agent_sessions')
|
|
244
|
-
.select('id, persona, instance_id')
|
|
245
|
-
.in('id', sessionIds);
|
|
246
|
-
sessionMap = (sessions || []).reduce((acc, s) => {
|
|
247
|
-
acc[s.id] = { persona: s.persona, instance_id: s.instance_id };
|
|
248
|
-
return acc;
|
|
249
|
-
}, {});
|
|
250
|
-
}
|
|
251
|
-
const checkouts = (data || []).map(c => ({
|
|
252
|
-
...c,
|
|
253
|
-
checked_out_by: c.checked_out_by_session_id
|
|
254
|
-
? sessionMap[c.checked_out_by_session_id]?.persona ||
|
|
255
|
-
sessionMap[c.checked_out_by_session_id]?.instance_id?.slice(0, 8) ||
|
|
256
|
-
'unknown'
|
|
257
|
-
: null,
|
|
258
|
-
}));
|
|
259
|
-
const activeCount = checkouts.filter(c => c.status === 'checked_out').length;
|
|
260
|
-
return {
|
|
261
|
-
result: {
|
|
262
|
-
checkouts,
|
|
263
|
-
active_count: activeCount,
|
|
264
|
-
total_count: checkouts.length,
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
};
|
|
268
|
-
/**
|
|
269
|
-
* Abandon a checkout (force release)
|
|
270
|
-
* Use when the original agent session died or is stuck
|
|
271
|
-
*/
|
|
272
|
-
export const abandonCheckout = async (args, ctx) => {
|
|
273
|
-
const { checkout_id, reason } = args;
|
|
274
|
-
validateRequired(checkout_id, 'checkout_id');
|
|
275
|
-
validateUUID(checkout_id, 'checkout_id');
|
|
276
|
-
const { supabase, session } = ctx;
|
|
277
|
-
// First get the checkout to verify project access
|
|
278
|
-
const { data: checkout } = await supabase
|
|
279
|
-
.from('file_checkouts')
|
|
280
|
-
.select('project_id')
|
|
281
|
-
.eq('id', checkout_id)
|
|
282
|
-
.single();
|
|
283
|
-
if (!checkout) {
|
|
284
|
-
return {
|
|
285
|
-
result: {
|
|
286
|
-
success: false,
|
|
287
|
-
error: 'Checkout not found',
|
|
288
|
-
},
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
// Verify user has access to this project
|
|
292
|
-
await verifyProjectAccess(ctx, checkout.project_id);
|
|
293
|
-
const { data, error } = await supabase
|
|
294
|
-
.from('file_checkouts')
|
|
295
|
-
.update({
|
|
296
|
-
status: 'abandoned',
|
|
297
|
-
checkin_summary: reason || 'Checkout abandoned',
|
|
298
|
-
checked_in_at: new Date().toISOString(),
|
|
299
|
-
checked_in_by_session_id: session.currentSessionId,
|
|
300
|
-
updated_at: new Date().toISOString(),
|
|
301
|
-
})
|
|
302
|
-
.eq('id', checkout_id)
|
|
303
|
-
.eq('status', 'checked_out')
|
|
304
|
-
.select('file_path')
|
|
305
|
-
.single();
|
|
306
|
-
if (error || !data) {
|
|
307
|
-
return {
|
|
308
|
-
result: {
|
|
309
|
-
success: false,
|
|
310
|
-
error: 'Failed to abandon checkout',
|
|
311
|
-
hint: 'The checkout may have already been checked in or abandoned',
|
|
312
|
-
},
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
return {
|
|
316
|
-
result: {
|
|
317
|
-
success: true,
|
|
318
|
-
checkout_id,
|
|
319
|
-
file_path: data.file_path,
|
|
320
|
-
message: 'Checkout abandoned successfully. File is now available.',
|
|
321
|
-
},
|
|
322
|
-
};
|
|
323
|
-
};
|
|
324
|
-
/**
|
|
325
|
-
* Check if a file is available for checkout
|
|
326
|
-
*/
|
|
327
|
-
export const isFileAvailable = async (args, ctx) => {
|
|
328
|
-
const { project_id, file_path } = args;
|
|
329
|
-
validateRequired(project_id, 'project_id');
|
|
330
|
-
validateUUID(project_id, 'project_id');
|
|
331
|
-
validateRequired(file_path, 'file_path');
|
|
332
|
-
// Verify user has access to this project
|
|
333
|
-
await verifyProjectAccess(ctx, project_id);
|
|
334
|
-
const { supabase } = ctx;
|
|
335
|
-
const { data } = await supabase
|
|
336
|
-
.from('file_checkouts')
|
|
337
|
-
.select('id, checked_out_by_session_id, checked_out_at')
|
|
338
|
-
.eq('project_id', project_id)
|
|
339
|
-
.eq('file_path', file_path)
|
|
340
|
-
.eq('status', 'checked_out')
|
|
341
|
-
.single();
|
|
342
|
-
if (!data) {
|
|
343
|
-
return {
|
|
344
|
-
result: {
|
|
345
|
-
available: true,
|
|
346
|
-
file_path,
|
|
347
|
-
},
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
// Get session info
|
|
351
|
-
const { data: sessionInfo } = await supabase
|
|
352
|
-
.from('agent_sessions')
|
|
353
|
-
.select('persona, instance_id')
|
|
354
|
-
.eq('id', data.checked_out_by_session_id)
|
|
355
|
-
.single();
|
|
356
|
-
const checkedOutBy = sessionInfo?.persona || sessionInfo?.instance_id?.slice(0, 8) || 'another agent';
|
|
357
|
-
const minutesAgo = Math.round((Date.now() - new Date(data.checked_out_at).getTime()) / 60000);
|
|
358
|
-
return {
|
|
359
|
-
result: {
|
|
360
|
-
available: false,
|
|
361
|
-
file_path,
|
|
362
|
-
checked_out_by: checkedOutBy,
|
|
363
|
-
checked_out_minutes_ago: minutesAgo,
|
|
364
|
-
checkout_id: data.id,
|
|
365
|
-
},
|
|
366
|
-
};
|
|
367
|
-
};
|
|
368
|
-
/**
|
|
369
|
-
* Checkout handlers registry
|
|
370
|
-
*/
|
|
371
|
-
export const checkoutHandlers = {
|
|
372
|
-
checkout_file: checkoutFile,
|
|
373
|
-
checkin_file: checkinFile,
|
|
374
|
-
get_file_checkouts: getFileCheckouts,
|
|
375
|
-
abandon_checkout: abandonCheckout,
|
|
376
|
-
is_file_available: isFileAvailable,
|
|
377
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Knowledge Query Handler
|
|
3
|
-
*
|
|
4
|
-
* Provides a queryable knowledge base from project data:
|
|
5
|
-
* - Findings (code quality, security, performance audits)
|
|
6
|
-
* - Questions and answers
|
|
7
|
-
* - Completed tasks with summaries
|
|
8
|
-
* - Decisions with rationales
|
|
9
|
-
* - Resolved blockers (lessons learned)
|
|
10
|
-
*
|
|
11
|
-
* Designed to reduce tool calls by aggregating multiple data sources in one query.
|
|
12
|
-
*/
|
|
13
|
-
import type { Handler, HandlerRegistry } from './types.js';
|
|
14
|
-
/**
|
|
15
|
-
* Query the knowledge base for aggregated project information.
|
|
16
|
-
* Replaces multiple tool calls with a single comprehensive query.
|
|
17
|
-
*/
|
|
18
|
-
export declare const queryKnowledgeBase: Handler;
|
|
19
|
-
/**
|
|
20
|
-
* Knowledge query handlers registry
|
|
21
|
-
*/
|
|
22
|
-
export declare const knowledgeQueryHandlers: HandlerRegistry;
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Knowledge Query Handler
|
|
3
|
-
*
|
|
4
|
-
* Provides a queryable knowledge base from project data:
|
|
5
|
-
* - Findings (code quality, security, performance audits)
|
|
6
|
-
* - Questions and answers
|
|
7
|
-
* - Completed tasks with summaries
|
|
8
|
-
* - Decisions with rationales
|
|
9
|
-
* - Resolved blockers (lessons learned)
|
|
10
|
-
*
|
|
11
|
-
* Designed to reduce tool calls by aggregating multiple data sources in one query.
|
|
12
|
-
*/
|
|
13
|
-
import { validateRequired, validateUUID } from '../validators.js';
|
|
14
|
-
/**
|
|
15
|
-
* Query the knowledge base for aggregated project information.
|
|
16
|
-
* Replaces multiple tool calls with a single comprehensive query.
|
|
17
|
-
*/
|
|
18
|
-
export const queryKnowledgeBase = async (args, ctx) => {
|
|
19
|
-
const { project_id, scope = 'summary', categories, limit = 5, search_query, } = args;
|
|
20
|
-
validateRequired(project_id, 'project_id');
|
|
21
|
-
validateUUID(project_id, 'project_id');
|
|
22
|
-
const { supabase } = ctx;
|
|
23
|
-
const effectiveLimit = Math.min(limit, 20); // Cap at 20 to prevent huge responses
|
|
24
|
-
// Default to all categories if not specified
|
|
25
|
-
const selectedCategories = new Set(categories || ['findings', 'qa', 'decisions', 'completed_tasks', 'blockers', 'progress']);
|
|
26
|
-
// Fetch project info first
|
|
27
|
-
const { data: project, error: projectError } = await supabase
|
|
28
|
-
.from('projects')
|
|
29
|
-
.select('name, goal, tech_stack')
|
|
30
|
-
.eq('id', project_id)
|
|
31
|
-
.single();
|
|
32
|
-
if (projectError || !project) {
|
|
33
|
-
return { result: { error: 'Project not found', project_id } };
|
|
34
|
-
}
|
|
35
|
-
// Fetch all stats counts in parallel
|
|
36
|
-
const [findingsStatsResult, qaCountResult, unansweredQaCountResult, decisionsCountResult, completedTasksCountResult, resolvedBlockersCountResult,] = await Promise.all([
|
|
37
|
-
supabase
|
|
38
|
-
.from('findings')
|
|
39
|
-
.select('severity', { count: 'exact' })
|
|
40
|
-
.eq('project_id', project_id)
|
|
41
|
-
.eq('status', 'open'),
|
|
42
|
-
supabase
|
|
43
|
-
.from('agent_requests')
|
|
44
|
-
.select('id', { count: 'exact', head: true })
|
|
45
|
-
.eq('project_id', project_id)
|
|
46
|
-
.eq('request_type', 'question'),
|
|
47
|
-
supabase
|
|
48
|
-
.from('agent_requests')
|
|
49
|
-
.select('id', { count: 'exact', head: true })
|
|
50
|
-
.eq('project_id', project_id)
|
|
51
|
-
.eq('request_type', 'question')
|
|
52
|
-
.is('answer', null),
|
|
53
|
-
supabase
|
|
54
|
-
.from('decisions')
|
|
55
|
-
.select('id', { count: 'exact', head: true })
|
|
56
|
-
.eq('project_id', project_id),
|
|
57
|
-
supabase
|
|
58
|
-
.from('tasks')
|
|
59
|
-
.select('id', { count: 'exact', head: true })
|
|
60
|
-
.eq('project_id', project_id)
|
|
61
|
-
.eq('status', 'completed'),
|
|
62
|
-
supabase
|
|
63
|
-
.from('blockers')
|
|
64
|
-
.select('id', { count: 'exact', head: true })
|
|
65
|
-
.eq('project_id', project_id)
|
|
66
|
-
.eq('status', 'resolved'),
|
|
67
|
-
]);
|
|
68
|
-
// Build stats
|
|
69
|
-
const severityCounts = {};
|
|
70
|
-
if (findingsStatsResult.data) {
|
|
71
|
-
for (const finding of findingsStatsResult.data) {
|
|
72
|
-
severityCounts[finding.severity] = (severityCounts[finding.severity] || 0) + 1;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const stats = {
|
|
76
|
-
findings_count: findingsStatsResult.count || 0,
|
|
77
|
-
open_findings_by_severity: severityCounts,
|
|
78
|
-
qa_count: qaCountResult.count || 0,
|
|
79
|
-
unanswered_qa_count: unansweredQaCountResult.count || 0,
|
|
80
|
-
decisions_count: decisionsCountResult.count || 0,
|
|
81
|
-
completed_tasks_count: completedTasksCountResult.count || 0,
|
|
82
|
-
resolved_blockers_count: resolvedBlockersCountResult.count || 0,
|
|
83
|
-
};
|
|
84
|
-
// Build result object
|
|
85
|
-
const result = {
|
|
86
|
-
project: {
|
|
87
|
-
name: project.name,
|
|
88
|
-
goal: project.goal || undefined,
|
|
89
|
-
tech_stack: project.tech_stack || undefined,
|
|
90
|
-
},
|
|
91
|
-
stats,
|
|
92
|
-
};
|
|
93
|
-
// Fetch detailed data for selected categories in parallel
|
|
94
|
-
const detailQueries = [];
|
|
95
|
-
if (selectedCategories.has('findings')) {
|
|
96
|
-
detailQueries.push((async () => {
|
|
97
|
-
let query = supabase
|
|
98
|
-
.from('findings')
|
|
99
|
-
.select('id, title, category, severity, file_path, status')
|
|
100
|
-
.eq('project_id', project_id)
|
|
101
|
-
.order('severity', { ascending: true })
|
|
102
|
-
.order('created_at', { ascending: false })
|
|
103
|
-
.limit(effectiveLimit);
|
|
104
|
-
if (search_query) {
|
|
105
|
-
query = query.or(`title.ilike.%${search_query}%,description.ilike.%${search_query}%`);
|
|
106
|
-
}
|
|
107
|
-
const { data } = await query;
|
|
108
|
-
if (data && data.length > 0) {
|
|
109
|
-
result.findings = data;
|
|
110
|
-
}
|
|
111
|
-
})());
|
|
112
|
-
}
|
|
113
|
-
if (selectedCategories.has('qa')) {
|
|
114
|
-
detailQueries.push((async () => {
|
|
115
|
-
let query = supabase
|
|
116
|
-
.from('agent_requests')
|
|
117
|
-
.select('id, message, answer, answered_at, created_at')
|
|
118
|
-
.eq('project_id', project_id)
|
|
119
|
-
.eq('request_type', 'question')
|
|
120
|
-
.order('created_at', { ascending: false })
|
|
121
|
-
.limit(effectiveLimit);
|
|
122
|
-
if (search_query) {
|
|
123
|
-
query = query.or(`message.ilike.%${search_query}%,answer.ilike.%${search_query}%`);
|
|
124
|
-
}
|
|
125
|
-
const { data } = await query;
|
|
126
|
-
if (data && data.length > 0) {
|
|
127
|
-
result.qa = data.map((q) => ({
|
|
128
|
-
id: q.id,
|
|
129
|
-
question: q.message,
|
|
130
|
-
answer: q.answer,
|
|
131
|
-
answered_at: q.answered_at,
|
|
132
|
-
created_at: q.created_at,
|
|
133
|
-
}));
|
|
134
|
-
}
|
|
135
|
-
})());
|
|
136
|
-
}
|
|
137
|
-
if (selectedCategories.has('decisions')) {
|
|
138
|
-
detailQueries.push((async () => {
|
|
139
|
-
// Always fetch full fields, the interface handles optional display
|
|
140
|
-
let query = supabase
|
|
141
|
-
.from('decisions')
|
|
142
|
-
.select('id, title, description, rationale, created_at')
|
|
143
|
-
.eq('project_id', project_id)
|
|
144
|
-
.order('created_at', { ascending: false })
|
|
145
|
-
.limit(effectiveLimit);
|
|
146
|
-
if (search_query) {
|
|
147
|
-
query = query.or(`title.ilike.%${search_query}%,description.ilike.%${search_query}%`);
|
|
148
|
-
}
|
|
149
|
-
const { data } = await query;
|
|
150
|
-
if (data && data.length > 0) {
|
|
151
|
-
result.decisions = data.map((d) => ({
|
|
152
|
-
id: d.id,
|
|
153
|
-
title: d.title,
|
|
154
|
-
description: scope === 'detailed' ? d.description : undefined,
|
|
155
|
-
rationale: scope === 'detailed' ? d.rationale : undefined,
|
|
156
|
-
created_at: d.created_at,
|
|
157
|
-
}));
|
|
158
|
-
}
|
|
159
|
-
})());
|
|
160
|
-
}
|
|
161
|
-
if (selectedCategories.has('completed_tasks')) {
|
|
162
|
-
detailQueries.push((async () => {
|
|
163
|
-
let query = supabase
|
|
164
|
-
.from('tasks')
|
|
165
|
-
.select('id, title, completion_summary, completed_at')
|
|
166
|
-
.eq('project_id', project_id)
|
|
167
|
-
.eq('status', 'completed')
|
|
168
|
-
.not('completed_at', 'is', null)
|
|
169
|
-
.order('completed_at', { ascending: false })
|
|
170
|
-
.limit(effectiveLimit);
|
|
171
|
-
if (search_query) {
|
|
172
|
-
query = query.or(`title.ilike.%${search_query}%,completion_summary.ilike.%${search_query}%`);
|
|
173
|
-
}
|
|
174
|
-
const { data } = await query;
|
|
175
|
-
if (data && data.length > 0) {
|
|
176
|
-
result.completed_tasks = data.map((t) => ({
|
|
177
|
-
id: t.id,
|
|
178
|
-
title: t.title,
|
|
179
|
-
summary: t.completion_summary || undefined,
|
|
180
|
-
completed_at: t.completed_at,
|
|
181
|
-
}));
|
|
182
|
-
}
|
|
183
|
-
})());
|
|
184
|
-
}
|
|
185
|
-
if (selectedCategories.has('blockers')) {
|
|
186
|
-
detailQueries.push((async () => {
|
|
187
|
-
let query = supabase
|
|
188
|
-
.from('blockers')
|
|
189
|
-
.select('id, description, resolution_note, resolved_at')
|
|
190
|
-
.eq('project_id', project_id)
|
|
191
|
-
.eq('status', 'resolved')
|
|
192
|
-
.order('resolved_at', { ascending: false })
|
|
193
|
-
.limit(effectiveLimit);
|
|
194
|
-
if (search_query) {
|
|
195
|
-
query = query.or(`description.ilike.%${search_query}%,resolution_note.ilike.%${search_query}%`);
|
|
196
|
-
}
|
|
197
|
-
const { data } = await query;
|
|
198
|
-
if (data && data.length > 0) {
|
|
199
|
-
result.resolved_blockers = data;
|
|
200
|
-
}
|
|
201
|
-
})());
|
|
202
|
-
}
|
|
203
|
-
if (selectedCategories.has('progress')) {
|
|
204
|
-
detailQueries.push((async () => {
|
|
205
|
-
let query = supabase
|
|
206
|
-
.from('progress_logs')
|
|
207
|
-
.select('id, summary, created_at, task_id')
|
|
208
|
-
.eq('project_id', project_id)
|
|
209
|
-
.order('created_at', { ascending: false })
|
|
210
|
-
.limit(effectiveLimit);
|
|
211
|
-
if (search_query) {
|
|
212
|
-
query = query.ilike('summary', `%${search_query}%`);
|
|
213
|
-
}
|
|
214
|
-
const { data } = await query;
|
|
215
|
-
if (data && data.length > 0) {
|
|
216
|
-
// Fetch task titles for progress logs that have task_id
|
|
217
|
-
const taskIds = data.filter((p) => p.task_id).map((p) => p.task_id);
|
|
218
|
-
let taskTitles = {};
|
|
219
|
-
if (taskIds.length > 0) {
|
|
220
|
-
const { data: tasks } = await supabase
|
|
221
|
-
.from('tasks')
|
|
222
|
-
.select('id, title')
|
|
223
|
-
.in('id', taskIds);
|
|
224
|
-
if (tasks) {
|
|
225
|
-
taskTitles = Object.fromEntries(tasks.map((t) => [t.id, t.title]));
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
result.recent_progress = data.map((p) => ({
|
|
229
|
-
id: p.id,
|
|
230
|
-
summary: p.summary,
|
|
231
|
-
task_title: p.task_id ? taskTitles[p.task_id] : undefined,
|
|
232
|
-
created_at: p.created_at,
|
|
233
|
-
}));
|
|
234
|
-
}
|
|
235
|
-
})());
|
|
236
|
-
}
|
|
237
|
-
// Execute all detail queries in parallel
|
|
238
|
-
await Promise.all(detailQueries);
|
|
239
|
-
return {
|
|
240
|
-
result,
|
|
241
|
-
// Add hint about token savings
|
|
242
|
-
_meta: {
|
|
243
|
-
categories_queried: Array.from(selectedCategories),
|
|
244
|
-
tool_calls_saved: selectedCategories.size, // Would have been N separate calls
|
|
245
|
-
},
|
|
246
|
-
};
|
|
247
|
-
};
|
|
248
|
-
/**
|
|
249
|
-
* Knowledge query handlers registry
|
|
250
|
-
*/
|
|
251
|
-
export const knowledgeQueryHandlers = {
|
|
252
|
-
query_knowledge_base: queryKnowledgeBase,
|
|
253
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Knowledge Handlers
|
|
3
|
-
*
|
|
4
|
-
* Unified knowledge query to reduce tool calls and token costs.
|
|
5
|
-
* Aggregates decisions, findings, blockers, Q&A, and task history.
|
|
6
|
-
*/
|
|
7
|
-
import type { Handler, HandlerRegistry } from './types.js';
|
|
8
|
-
export declare const queryKnowledge: Handler;
|
|
9
|
-
/**
|
|
10
|
-
* Knowledge handlers registry
|
|
11
|
-
*/
|
|
12
|
-
export declare const knowledgeHandlers: HandlerRegistry;
|