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