@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.
Files changed (173) hide show
  1. package/README.md +113 -98
  2. package/dist/api-client.d.ts +1169 -0
  3. package/dist/api-client.js +713 -0
  4. package/dist/cli.d.ts +1 -6
  5. package/dist/cli.js +39 -240
  6. package/dist/config/tool-categories.d.ts +31 -0
  7. package/dist/config/tool-categories.js +253 -0
  8. package/dist/handlers/blockers.js +57 -58
  9. package/dist/handlers/bodies-of-work.d.ts +2 -0
  10. package/dist/handlers/bodies-of-work.js +108 -477
  11. package/dist/handlers/cost.d.ts +1 -0
  12. package/dist/handlers/cost.js +35 -113
  13. package/dist/handlers/decisions.d.ts +2 -0
  14. package/dist/handlers/decisions.js +28 -27
  15. package/dist/handlers/deployment.js +113 -828
  16. package/dist/handlers/discovery.d.ts +3 -0
  17. package/dist/handlers/discovery.js +26 -627
  18. package/dist/handlers/fallback.d.ts +2 -0
  19. package/dist/handlers/fallback.js +56 -142
  20. package/dist/handlers/findings.d.ts +8 -1
  21. package/dist/handlers/findings.js +65 -68
  22. package/dist/handlers/git-issues.d.ts +9 -13
  23. package/dist/handlers/git-issues.js +80 -225
  24. package/dist/handlers/ideas.d.ts +3 -0
  25. package/dist/handlers/ideas.js +53 -134
  26. package/dist/handlers/index.d.ts +2 -0
  27. package/dist/handlers/index.js +6 -0
  28. package/dist/handlers/milestones.d.ts +2 -0
  29. package/dist/handlers/milestones.js +51 -98
  30. package/dist/handlers/organizations.js +79 -275
  31. package/dist/handlers/progress.d.ts +2 -0
  32. package/dist/handlers/progress.js +25 -123
  33. package/dist/handlers/project.js +42 -221
  34. package/dist/handlers/requests.d.ts +2 -0
  35. package/dist/handlers/requests.js +23 -83
  36. package/dist/handlers/session.js +119 -590
  37. package/dist/handlers/sprints.d.ts +32 -0
  38. package/dist/handlers/sprints.js +275 -0
  39. package/dist/handlers/tasks.d.ts +7 -10
  40. package/dist/handlers/tasks.js +245 -894
  41. package/dist/handlers/tool-docs.d.ts +9 -0
  42. package/dist/handlers/tool-docs.js +904 -0
  43. package/dist/handlers/types.d.ts +11 -3
  44. package/dist/handlers/validation.d.ts +1 -1
  45. package/dist/handlers/validation.js +38 -153
  46. package/dist/index.js +493 -162
  47. package/dist/knowledge.js +106 -9
  48. package/dist/tools.js +34 -4
  49. package/dist/validators.d.ts +21 -0
  50. package/dist/validators.js +91 -0
  51. package/package.json +2 -3
  52. package/src/api-client.ts +1822 -0
  53. package/src/cli.test.ts +128 -302
  54. package/src/cli.ts +41 -285
  55. package/src/handlers/__test-setup__.ts +215 -0
  56. package/src/handlers/__test-utils__.ts +4 -134
  57. package/src/handlers/blockers.test.ts +114 -124
  58. package/src/handlers/blockers.ts +68 -70
  59. package/src/handlers/bodies-of-work.test.ts +236 -831
  60. package/src/handlers/bodies-of-work.ts +210 -525
  61. package/src/handlers/cost.test.ts +149 -113
  62. package/src/handlers/cost.ts +44 -132
  63. package/src/handlers/decisions.test.ts +111 -209
  64. package/src/handlers/decisions.ts +35 -27
  65. package/src/handlers/deployment.test.ts +193 -239
  66. package/src/handlers/deployment.ts +143 -896
  67. package/src/handlers/discovery.test.ts +20 -67
  68. package/src/handlers/discovery.ts +29 -714
  69. package/src/handlers/fallback.test.ts +206 -361
  70. package/src/handlers/fallback.ts +81 -156
  71. package/src/handlers/findings.test.ts +229 -320
  72. package/src/handlers/findings.ts +76 -64
  73. package/src/handlers/git-issues.test.ts +623 -0
  74. package/src/handlers/git-issues.ts +174 -0
  75. package/src/handlers/ideas.test.ts +229 -343
  76. package/src/handlers/ideas.ts +69 -143
  77. package/src/handlers/index.ts +6 -0
  78. package/src/handlers/milestones.test.ts +167 -281
  79. package/src/handlers/milestones.ts +54 -93
  80. package/src/handlers/organizations.test.ts +275 -467
  81. package/src/handlers/organizations.ts +84 -294
  82. package/src/handlers/progress.test.ts +112 -218
  83. package/src/handlers/progress.ts +29 -142
  84. package/src/handlers/project.test.ts +203 -226
  85. package/src/handlers/project.ts +48 -238
  86. package/src/handlers/requests.test.ts +74 -342
  87. package/src/handlers/requests.ts +25 -83
  88. package/src/handlers/session.test.ts +276 -206
  89. package/src/handlers/session.ts +136 -662
  90. package/src/handlers/sprints.test.ts +711 -0
  91. package/src/handlers/sprints.ts +510 -0
  92. package/src/handlers/tasks.test.ts +669 -353
  93. package/src/handlers/tasks.ts +263 -1015
  94. package/src/handlers/tool-docs.ts +1024 -0
  95. package/src/handlers/types.ts +12 -4
  96. package/src/handlers/validation.test.ts +237 -568
  97. package/src/handlers/validation.ts +43 -167
  98. package/src/index.ts +493 -186
  99. package/src/tools.ts +2532 -0
  100. package/src/validators.test.ts +223 -223
  101. package/src/validators.ts +127 -0
  102. package/tsconfig.json +1 -1
  103. package/vitest.config.ts +14 -13
  104. package/dist/cli.test.d.ts +0 -1
  105. package/dist/cli.test.js +0 -367
  106. package/dist/handlers/__test-utils__.d.ts +0 -72
  107. package/dist/handlers/__test-utils__.js +0 -176
  108. package/dist/handlers/checkouts.d.ts +0 -37
  109. package/dist/handlers/checkouts.js +0 -377
  110. package/dist/handlers/knowledge-query.d.ts +0 -22
  111. package/dist/handlers/knowledge-query.js +0 -253
  112. package/dist/handlers/knowledge.d.ts +0 -12
  113. package/dist/handlers/knowledge.js +0 -108
  114. package/dist/handlers/roles.d.ts +0 -30
  115. package/dist/handlers/roles.js +0 -281
  116. package/dist/handlers/tasks.test.d.ts +0 -1
  117. package/dist/handlers/tasks.test.js +0 -431
  118. package/dist/utils.test.d.ts +0 -1
  119. package/dist/utils.test.js +0 -532
  120. package/dist/validators.test.d.ts +0 -1
  121. package/dist/validators.test.js +0 -176
  122. package/src/knowledge.ts +0 -132
  123. package/src/tmpclaude-0078-cwd +0 -1
  124. package/src/tmpclaude-0ee1-cwd +0 -1
  125. package/src/tmpclaude-2dd5-cwd +0 -1
  126. package/src/tmpclaude-344c-cwd +0 -1
  127. package/src/tmpclaude-3860-cwd +0 -1
  128. package/src/tmpclaude-4b63-cwd +0 -1
  129. package/src/tmpclaude-5c73-cwd +0 -1
  130. package/src/tmpclaude-5ee3-cwd +0 -1
  131. package/src/tmpclaude-6795-cwd +0 -1
  132. package/src/tmpclaude-709e-cwd +0 -1
  133. package/src/tmpclaude-9839-cwd +0 -1
  134. package/src/tmpclaude-d829-cwd +0 -1
  135. package/src/tmpclaude-e072-cwd +0 -1
  136. package/src/tmpclaude-f6ee-cwd +0 -1
  137. package/tmpclaude-0439-cwd +0 -1
  138. package/tmpclaude-132f-cwd +0 -1
  139. package/tmpclaude-15bb-cwd +0 -1
  140. package/tmpclaude-165a-cwd +0 -1
  141. package/tmpclaude-1ba9-cwd +0 -1
  142. package/tmpclaude-21a3-cwd +0 -1
  143. package/tmpclaude-2a38-cwd +0 -1
  144. package/tmpclaude-2adf-cwd +0 -1
  145. package/tmpclaude-2f56-cwd +0 -1
  146. package/tmpclaude-3626-cwd +0 -1
  147. package/tmpclaude-3727-cwd +0 -1
  148. package/tmpclaude-40bc-cwd +0 -1
  149. package/tmpclaude-436f-cwd +0 -1
  150. package/tmpclaude-4783-cwd +0 -1
  151. package/tmpclaude-4b6d-cwd +0 -1
  152. package/tmpclaude-4ba4-cwd +0 -1
  153. package/tmpclaude-51e6-cwd +0 -1
  154. package/tmpclaude-5ecf-cwd +0 -1
  155. package/tmpclaude-6f97-cwd +0 -1
  156. package/tmpclaude-7fb2-cwd +0 -1
  157. package/tmpclaude-825c-cwd +0 -1
  158. package/tmpclaude-8baf-cwd +0 -1
  159. package/tmpclaude-8d9f-cwd +0 -1
  160. package/tmpclaude-975c-cwd +0 -1
  161. package/tmpclaude-9983-cwd +0 -1
  162. package/tmpclaude-a045-cwd +0 -1
  163. package/tmpclaude-ac4a-cwd +0 -1
  164. package/tmpclaude-b593-cwd +0 -1
  165. package/tmpclaude-b891-cwd +0 -1
  166. package/tmpclaude-c032-cwd +0 -1
  167. package/tmpclaude-cf43-cwd +0 -1
  168. package/tmpclaude-d040-cwd +0 -1
  169. package/tmpclaude-dcdd-cwd +0 -1
  170. package/tmpclaude-dcee-cwd +0 -1
  171. package/tmpclaude-e16b-cwd +0 -1
  172. package/tmpclaude-ecd2-cwd +0 -1
  173. 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;