@vibescope/mcp-server 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +98 -0
  2. package/dist/cli.d.ts +34 -0
  3. package/dist/cli.js +356 -0
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +367 -0
  6. package/dist/handlers/__test-utils__.d.ts +72 -0
  7. package/dist/handlers/__test-utils__.js +176 -0
  8. package/dist/handlers/blockers.d.ts +18 -0
  9. package/dist/handlers/blockers.js +81 -0
  10. package/dist/handlers/bodies-of-work.d.ts +34 -0
  11. package/dist/handlers/bodies-of-work.js +614 -0
  12. package/dist/handlers/checkouts.d.ts +37 -0
  13. package/dist/handlers/checkouts.js +377 -0
  14. package/dist/handlers/cost.d.ts +39 -0
  15. package/dist/handlers/cost.js +247 -0
  16. package/dist/handlers/decisions.d.ts +16 -0
  17. package/dist/handlers/decisions.js +64 -0
  18. package/dist/handlers/deployment.d.ts +36 -0
  19. package/dist/handlers/deployment.js +1062 -0
  20. package/dist/handlers/discovery.d.ts +14 -0
  21. package/dist/handlers/discovery.js +870 -0
  22. package/dist/handlers/fallback.d.ts +18 -0
  23. package/dist/handlers/fallback.js +216 -0
  24. package/dist/handlers/findings.d.ts +18 -0
  25. package/dist/handlers/findings.js +110 -0
  26. package/dist/handlers/git-issues.d.ts +22 -0
  27. package/dist/handlers/git-issues.js +247 -0
  28. package/dist/handlers/ideas.d.ts +19 -0
  29. package/dist/handlers/ideas.js +188 -0
  30. package/dist/handlers/index.d.ts +29 -0
  31. package/dist/handlers/index.js +65 -0
  32. package/dist/handlers/knowledge-query.d.ts +22 -0
  33. package/dist/handlers/knowledge-query.js +253 -0
  34. package/dist/handlers/knowledge.d.ts +12 -0
  35. package/dist/handlers/knowledge.js +108 -0
  36. package/dist/handlers/milestones.d.ts +20 -0
  37. package/dist/handlers/milestones.js +179 -0
  38. package/dist/handlers/organizations.d.ts +36 -0
  39. package/dist/handlers/organizations.js +428 -0
  40. package/dist/handlers/progress.d.ts +14 -0
  41. package/dist/handlers/progress.js +149 -0
  42. package/dist/handlers/project.d.ts +20 -0
  43. package/dist/handlers/project.js +278 -0
  44. package/dist/handlers/requests.d.ts +16 -0
  45. package/dist/handlers/requests.js +131 -0
  46. package/dist/handlers/roles.d.ts +30 -0
  47. package/dist/handlers/roles.js +281 -0
  48. package/dist/handlers/session.d.ts +20 -0
  49. package/dist/handlers/session.js +791 -0
  50. package/dist/handlers/tasks.d.ts +52 -0
  51. package/dist/handlers/tasks.js +1111 -0
  52. package/dist/handlers/tasks.test.d.ts +1 -0
  53. package/dist/handlers/tasks.test.js +431 -0
  54. package/dist/handlers/types.d.ts +94 -0
  55. package/dist/handlers/types.js +1 -0
  56. package/dist/handlers/validation.d.ts +16 -0
  57. package/dist/handlers/validation.js +188 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +2707 -0
  60. package/dist/knowledge.d.ts +6 -0
  61. package/dist/knowledge.js +121 -0
  62. package/dist/tools.d.ts +2 -0
  63. package/dist/tools.js +2498 -0
  64. package/dist/utils.d.ts +149 -0
  65. package/dist/utils.js +317 -0
  66. package/dist/utils.test.d.ts +1 -0
  67. package/dist/utils.test.js +532 -0
  68. package/dist/validators.d.ts +35 -0
  69. package/dist/validators.js +111 -0
  70. package/dist/validators.test.d.ts +1 -0
  71. package/dist/validators.test.js +176 -0
  72. package/package.json +44 -0
  73. package/src/cli.test.ts +442 -0
  74. package/src/cli.ts +439 -0
  75. package/src/handlers/__test-utils__.ts +217 -0
  76. package/src/handlers/blockers.test.ts +390 -0
  77. package/src/handlers/blockers.ts +110 -0
  78. package/src/handlers/bodies-of-work.test.ts +1276 -0
  79. package/src/handlers/bodies-of-work.ts +783 -0
  80. package/src/handlers/cost.test.ts +436 -0
  81. package/src/handlers/cost.ts +322 -0
  82. package/src/handlers/decisions.test.ts +401 -0
  83. package/src/handlers/decisions.ts +86 -0
  84. package/src/handlers/deployment.test.ts +516 -0
  85. package/src/handlers/deployment.ts +1289 -0
  86. package/src/handlers/discovery.test.ts +254 -0
  87. package/src/handlers/discovery.ts +969 -0
  88. package/src/handlers/fallback.test.ts +687 -0
  89. package/src/handlers/fallback.ts +260 -0
  90. package/src/handlers/findings.test.ts +565 -0
  91. package/src/handlers/findings.ts +153 -0
  92. package/src/handlers/ideas.test.ts +753 -0
  93. package/src/handlers/ideas.ts +247 -0
  94. package/src/handlers/index.ts +69 -0
  95. package/src/handlers/milestones.test.ts +584 -0
  96. package/src/handlers/milestones.ts +217 -0
  97. package/src/handlers/organizations.test.ts +997 -0
  98. package/src/handlers/organizations.ts +550 -0
  99. package/src/handlers/progress.test.ts +369 -0
  100. package/src/handlers/progress.ts +188 -0
  101. package/src/handlers/project.test.ts +562 -0
  102. package/src/handlers/project.ts +352 -0
  103. package/src/handlers/requests.test.ts +531 -0
  104. package/src/handlers/requests.ts +150 -0
  105. package/src/handlers/session.test.ts +459 -0
  106. package/src/handlers/session.ts +912 -0
  107. package/src/handlers/tasks.test.ts +602 -0
  108. package/src/handlers/tasks.ts +1393 -0
  109. package/src/handlers/types.ts +88 -0
  110. package/src/handlers/validation.test.ts +880 -0
  111. package/src/handlers/validation.ts +223 -0
  112. package/src/index.ts +3205 -0
  113. package/src/knowledge.ts +132 -0
  114. package/src/tmpclaude-0078-cwd +1 -0
  115. package/src/tmpclaude-0ee1-cwd +1 -0
  116. package/src/tmpclaude-2dd5-cwd +1 -0
  117. package/src/tmpclaude-344c-cwd +1 -0
  118. package/src/tmpclaude-3860-cwd +1 -0
  119. package/src/tmpclaude-4b63-cwd +1 -0
  120. package/src/tmpclaude-5c73-cwd +1 -0
  121. package/src/tmpclaude-5ee3-cwd +1 -0
  122. package/src/tmpclaude-6795-cwd +1 -0
  123. package/src/tmpclaude-709e-cwd +1 -0
  124. package/src/tmpclaude-9839-cwd +1 -0
  125. package/src/tmpclaude-d829-cwd +1 -0
  126. package/src/tmpclaude-e072-cwd +1 -0
  127. package/src/tmpclaude-f6ee-cwd +1 -0
  128. package/src/utils.test.ts +681 -0
  129. package/src/utils.ts +375 -0
  130. package/src/validators.test.ts +223 -0
  131. package/src/validators.ts +122 -0
  132. package/tmpclaude-0439-cwd +1 -0
  133. package/tmpclaude-132f-cwd +1 -0
  134. package/tmpclaude-15bb-cwd +1 -0
  135. package/tmpclaude-165a-cwd +1 -0
  136. package/tmpclaude-1ba9-cwd +1 -0
  137. package/tmpclaude-21a3-cwd +1 -0
  138. package/tmpclaude-2a38-cwd +1 -0
  139. package/tmpclaude-2adf-cwd +1 -0
  140. package/tmpclaude-2f56-cwd +1 -0
  141. package/tmpclaude-3626-cwd +1 -0
  142. package/tmpclaude-3727-cwd +1 -0
  143. package/tmpclaude-40bc-cwd +1 -0
  144. package/tmpclaude-436f-cwd +1 -0
  145. package/tmpclaude-4783-cwd +1 -0
  146. package/tmpclaude-4b6d-cwd +1 -0
  147. package/tmpclaude-4ba4-cwd +1 -0
  148. package/tmpclaude-51e6-cwd +1 -0
  149. package/tmpclaude-5ecf-cwd +1 -0
  150. package/tmpclaude-6f97-cwd +1 -0
  151. package/tmpclaude-7fb2-cwd +1 -0
  152. package/tmpclaude-825c-cwd +1 -0
  153. package/tmpclaude-8baf-cwd +1 -0
  154. package/tmpclaude-8d9f-cwd +1 -0
  155. package/tmpclaude-975c-cwd +1 -0
  156. package/tmpclaude-9983-cwd +1 -0
  157. package/tmpclaude-a045-cwd +1 -0
  158. package/tmpclaude-ac4a-cwd +1 -0
  159. package/tmpclaude-b593-cwd +1 -0
  160. package/tmpclaude-b891-cwd +1 -0
  161. package/tmpclaude-c032-cwd +1 -0
  162. package/tmpclaude-cf43-cwd +1 -0
  163. package/tmpclaude-d040-cwd +1 -0
  164. package/tmpclaude-dcdd-cwd +1 -0
  165. package/tmpclaude-dcee-cwd +1 -0
  166. package/tmpclaude-e16b-cwd +1 -0
  167. package/tmpclaude-ecd2-cwd +1 -0
  168. package/tmpclaude-f48d-cwd +1 -0
  169. package/tsconfig.json +16 -0
  170. package/vitest.config.ts +13 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Ideas Handlers
3
+ *
4
+ * Handles feature ideas tracking:
5
+ * - add_idea
6
+ * - update_idea
7
+ * - get_ideas
8
+ * - delete_idea
9
+ */
10
+ import { validateRequired, validateUUID, validatePriority, validateEstimatedMinutes } from '../validators.js';
11
+ export const addIdea = async (args, ctx) => {
12
+ const { project_id, title, description, status } = args;
13
+ validateRequired(project_id, 'project_id');
14
+ validateUUID(project_id, 'project_id');
15
+ validateRequired(title, 'title');
16
+ const { supabase, session } = ctx;
17
+ const { data, error } = await supabase
18
+ .from('ideas')
19
+ .insert({
20
+ project_id,
21
+ title,
22
+ description: description || null,
23
+ status: status || 'raw',
24
+ created_by: 'agent',
25
+ created_by_session_id: session.currentSessionId,
26
+ })
27
+ .select('id')
28
+ .single();
29
+ if (error)
30
+ throw new Error(`Failed to add idea: ${error.message}`);
31
+ return { result: { success: true, idea_id: data.id, title } };
32
+ };
33
+ export const updateIdea = async (args, ctx) => {
34
+ const { idea_id, title, description, status, doc_url } = args;
35
+ validateRequired(idea_id, 'idea_id');
36
+ validateUUID(idea_id, 'idea_id');
37
+ const { supabase } = ctx;
38
+ // Get current idea status
39
+ const { data: existingIdea } = await supabase
40
+ .from('ideas')
41
+ .select('status')
42
+ .eq('id', idea_id)
43
+ .single();
44
+ if (!existingIdea) {
45
+ throw new Error(`Idea not found: ${idea_id}`);
46
+ }
47
+ // Build update object
48
+ const updates = { updated_at: new Date().toISOString() };
49
+ if (title !== undefined)
50
+ updates.title = title;
51
+ if (description !== undefined)
52
+ updates.description = description;
53
+ if (doc_url !== undefined)
54
+ updates.doc_url = doc_url;
55
+ if (status !== undefined) {
56
+ updates.status = status;
57
+ // Set planned_at when transitioning to planned status
58
+ if (status === 'planned' && existingIdea.status !== 'planned') {
59
+ updates.planned_at = new Date().toISOString();
60
+ }
61
+ }
62
+ const { error } = await supabase
63
+ .from('ideas')
64
+ .update(updates)
65
+ .eq('id', idea_id);
66
+ if (error)
67
+ throw new Error(`Failed to update idea: ${error.message}`);
68
+ return { result: { success: true, idea_id } };
69
+ };
70
+ export const getIdeas = async (args, ctx) => {
71
+ const { project_id, status } = args;
72
+ validateRequired(project_id, 'project_id');
73
+ validateUUID(project_id, 'project_id');
74
+ const { supabase } = ctx;
75
+ let query = supabase
76
+ .from('ideas')
77
+ .select('id, title, description, status, doc_url')
78
+ .eq('project_id', project_id);
79
+ if (status) {
80
+ query = query.eq('status', status);
81
+ }
82
+ const { data, error } = await query.order('created_at', { ascending: false });
83
+ if (error)
84
+ throw new Error(`Failed to fetch ideas: ${error.message}`);
85
+ return { result: { ideas: data || [] } };
86
+ };
87
+ export const deleteIdea = async (args, ctx) => {
88
+ const { idea_id } = args;
89
+ validateRequired(idea_id, 'idea_id');
90
+ validateUUID(idea_id, 'idea_id');
91
+ const { error } = await ctx.supabase
92
+ .from('ideas')
93
+ .delete()
94
+ .eq('id', idea_id);
95
+ if (error)
96
+ throw new Error(`Failed to delete idea: ${error.message}`);
97
+ return { result: { success: true } };
98
+ };
99
+ export const convertIdeaToTask = async (args, ctx) => {
100
+ const { idea_id, priority = 3, estimated_minutes, update_status = true } = args;
101
+ validateRequired(idea_id, 'idea_id');
102
+ validateUUID(idea_id, 'idea_id');
103
+ validatePriority(priority);
104
+ validateEstimatedMinutes(estimated_minutes);
105
+ const { supabase, session } = ctx;
106
+ const currentSessionId = session.currentSessionId;
107
+ // Get the idea
108
+ const { data: idea, error: fetchError } = await supabase
109
+ .from('ideas')
110
+ .select('id, project_id, title, description, status, converted_to_task_id')
111
+ .eq('id', idea_id)
112
+ .single();
113
+ if (fetchError || !idea) {
114
+ throw new Error(`Idea not found: ${idea_id}`);
115
+ }
116
+ // Check if already converted
117
+ if (idea.converted_to_task_id) {
118
+ return {
119
+ result: {
120
+ success: false,
121
+ error: 'Idea has already been converted to a task',
122
+ existing_task_id: idea.converted_to_task_id,
123
+ hint: 'Use get_tasks to find the existing task',
124
+ },
125
+ };
126
+ }
127
+ // Create the task
128
+ const { data: task, error: taskError } = await supabase
129
+ .from('tasks')
130
+ .insert({
131
+ project_id: idea.project_id,
132
+ title: idea.title,
133
+ description: idea.description || `Converted from idea: ${idea.title}`,
134
+ priority,
135
+ estimated_minutes: estimated_minutes || null,
136
+ created_by: 'agent',
137
+ created_by_session_id: currentSessionId,
138
+ })
139
+ .select('id, title')
140
+ .single();
141
+ if (taskError || !task) {
142
+ throw new Error(`Failed to create task: ${taskError?.message}`);
143
+ }
144
+ // Update the idea with the task reference and optionally update status
145
+ const ideaUpdates = {
146
+ converted_to_task_id: task.id,
147
+ updated_at: new Date().toISOString(),
148
+ };
149
+ if (update_status && idea.status !== 'shipped') {
150
+ ideaUpdates.status = 'in_development';
151
+ }
152
+ const { error: updateError } = await supabase
153
+ .from('ideas')
154
+ .update(ideaUpdates)
155
+ .eq('id', idea_id);
156
+ if (updateError) {
157
+ // Log but don't fail - task was created successfully
158
+ console.error(`Failed to update idea after conversion: ${updateError.message}`);
159
+ }
160
+ // Log progress
161
+ await supabase.from('progress_logs').insert({
162
+ project_id: idea.project_id,
163
+ task_id: task.id,
164
+ summary: `Converted idea "${idea.title}" to task`,
165
+ created_by: 'agent',
166
+ created_by_session_id: currentSessionId,
167
+ });
168
+ return {
169
+ result: {
170
+ success: true,
171
+ task_id: task.id,
172
+ task_title: task.title,
173
+ idea_id: idea.id,
174
+ idea_status: update_status ? 'in_development' : idea.status,
175
+ message: `Created task from idea. Start with: update_task(task_id: "${task.id}", status: "in_progress")`,
176
+ },
177
+ };
178
+ };
179
+ /**
180
+ * Ideas handlers registry
181
+ */
182
+ export const ideaHandlers = {
183
+ add_idea: addIdea,
184
+ update_idea: updateIdea,
185
+ get_ideas: getIdeas,
186
+ delete_idea: deleteIdea,
187
+ convert_idea_to_task: convertIdeaToTask,
188
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * MCP Server Handlers
3
+ *
4
+ * This module exports all tool handlers organized by category.
5
+ * Each handler receives a HandlerContext and returns a HandlerResult.
6
+ */
7
+ export * from './types.js';
8
+ export * from './milestones.js';
9
+ export * from './session.js';
10
+ export * from './ideas.js';
11
+ export * from './findings.js';
12
+ export * from './blockers.js';
13
+ export * from './decisions.js';
14
+ export * from './progress.js';
15
+ export * from './requests.js';
16
+ export * from './tasks.js';
17
+ export * from './project.js';
18
+ export * from './deployment.js';
19
+ export * from './validation.js';
20
+ export * from './fallback.js';
21
+ export * from './bodies-of-work.js';
22
+ export * from './discovery.js';
23
+ export * from './organizations.js';
24
+ export * from './cost.js';
25
+ import type { HandlerRegistry } from './types.js';
26
+ /**
27
+ * Build the complete handler registry from all modules
28
+ */
29
+ export declare function buildHandlerRegistry(): HandlerRegistry;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * MCP Server Handlers
3
+ *
4
+ * This module exports all tool handlers organized by category.
5
+ * Each handler receives a HandlerContext and returns a HandlerResult.
6
+ */
7
+ export * from './types.js';
8
+ export * from './milestones.js';
9
+ export * from './session.js';
10
+ export * from './ideas.js';
11
+ export * from './findings.js';
12
+ export * from './blockers.js';
13
+ export * from './decisions.js';
14
+ export * from './progress.js';
15
+ export * from './requests.js';
16
+ export * from './tasks.js';
17
+ export * from './project.js';
18
+ export * from './deployment.js';
19
+ export * from './validation.js';
20
+ export * from './fallback.js';
21
+ export * from './bodies-of-work.js';
22
+ export * from './discovery.js';
23
+ export * from './organizations.js';
24
+ export * from './cost.js';
25
+ import { milestoneHandlers } from './milestones.js';
26
+ import { sessionHandlers } from './session.js';
27
+ import { ideaHandlers } from './ideas.js';
28
+ import { findingHandlers } from './findings.js';
29
+ import { blockerHandlers } from './blockers.js';
30
+ import { decisionHandlers } from './decisions.js';
31
+ import { progressHandlers } from './progress.js';
32
+ import { requestHandlers } from './requests.js';
33
+ import { taskHandlers } from './tasks.js';
34
+ import { projectHandlers } from './project.js';
35
+ import { deploymentHandlers } from './deployment.js';
36
+ import { validationHandlers } from './validation.js';
37
+ import { fallbackHandlers } from './fallback.js';
38
+ import { bodiesOfWorkHandlers } from './bodies-of-work.js';
39
+ import { discoveryHandlers } from './discovery.js';
40
+ import { organizationHandlers } from './organizations.js';
41
+ import { costHandlers } from './cost.js';
42
+ /**
43
+ * Build the complete handler registry from all modules
44
+ */
45
+ export function buildHandlerRegistry() {
46
+ return {
47
+ ...milestoneHandlers,
48
+ ...sessionHandlers,
49
+ ...ideaHandlers,
50
+ ...findingHandlers,
51
+ ...blockerHandlers,
52
+ ...decisionHandlers,
53
+ ...progressHandlers,
54
+ ...requestHandlers,
55
+ ...taskHandlers,
56
+ ...projectHandlers,
57
+ ...deploymentHandlers,
58
+ ...validationHandlers,
59
+ ...fallbackHandlers,
60
+ ...bodiesOfWorkHandlers,
61
+ ...discoveryHandlers,
62
+ ...organizationHandlers,
63
+ ...costHandlers,
64
+ };
65
+ }
@@ -0,0 +1,22 @@
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;
@@ -0,0 +1,253 @@
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
+ };
@@ -0,0 +1,12 @@
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;
@@ -0,0 +1,108 @@
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 { validateRequired, validateUUID } from '../validators.js';
8
+ const VALID_QUERY_TYPES = ['context', 'architecture', 'issues', 'history', 'qa'];
9
+ const QUERY_TYPE_DESCRIPTIONS = {
10
+ context: 'Key decisions, open blockers, recent Q&As, critical findings - use when starting work',
11
+ architecture: 'All decisions + architecture-related findings - use when understanding codebase',
12
+ issues: 'Open findings + blockers sorted by severity - use when fixing bugs',
13
+ history: 'Completed tasks with summaries - use when understanding what was done',
14
+ qa: 'Answered questions as Q&A pairs - use when looking for common questions',
15
+ };
16
+ export const queryKnowledge = async (args, ctx) => {
17
+ const { project_id, query_type = 'context', limit = 20 } = args;
18
+ validateRequired(project_id, 'project_id');
19
+ validateUUID(project_id, 'project_id');
20
+ if (!VALID_QUERY_TYPES.includes(query_type)) {
21
+ throw new Error(`Invalid query_type '${query_type}'. Must be one of: ${VALID_QUERY_TYPES.join(', ')}`);
22
+ }
23
+ const { supabase } = ctx;
24
+ // Call the database function
25
+ const { data, error } = await supabase.rpc('query_knowledge', {
26
+ p_project_id: project_id,
27
+ p_query_type: query_type,
28
+ p_limit: Math.min(limit, 50), // Cap at 50 to limit response size
29
+ });
30
+ if (error) {
31
+ throw new Error(`Failed to query knowledge: ${error.message}`);
32
+ }
33
+ // Map database results to typed items
34
+ const items = (data || []).map((row) => ({
35
+ type: row.item_type,
36
+ id: row.id,
37
+ title: row.title,
38
+ content: row.content,
39
+ ...(row.severity && { severity: row.severity }),
40
+ ...(row.status && { status: row.status }),
41
+ created_at: row.created_at,
42
+ }));
43
+ // Generate summary based on query type and results
44
+ const summary = generateSummary(query_type, items);
45
+ // Estimate tokens (rough: ~4 chars per token)
46
+ const responseJson = JSON.stringify({ items, summary });
47
+ const tokenEstimate = Math.ceil(responseJson.length / 4);
48
+ return {
49
+ result: {
50
+ query_type,
51
+ project_id,
52
+ description: QUERY_TYPE_DESCRIPTIONS[query_type],
53
+ summary,
54
+ items,
55
+ count: items.length,
56
+ token_estimate: tokenEstimate,
57
+ },
58
+ };
59
+ };
60
+ function generateSummary(queryType, items) {
61
+ const counts = items.reduce((acc, item) => {
62
+ acc[item.type] = (acc[item.type] || 0) + 1;
63
+ return acc;
64
+ }, {});
65
+ const parts = [];
66
+ switch (queryType) {
67
+ case 'context':
68
+ if (counts.blocker)
69
+ parts.push(`${counts.blocker} open blocker(s)`);
70
+ if (counts.finding)
71
+ parts.push(`${counts.finding} critical/high finding(s)`);
72
+ if (counts.decision)
73
+ parts.push(`${counts.decision} recent decision(s)`);
74
+ if (counts.qa)
75
+ parts.push(`${counts.qa} answered question(s)`);
76
+ break;
77
+ case 'architecture':
78
+ if (counts.decision)
79
+ parts.push(`${counts.decision} architectural decision(s)`);
80
+ if (counts.finding)
81
+ parts.push(`${counts.finding} architecture finding(s)`);
82
+ break;
83
+ case 'issues':
84
+ if (counts.blocker)
85
+ parts.push(`${counts.blocker} blocker(s)`);
86
+ if (counts.finding)
87
+ parts.push(`${counts.finding} open finding(s)`);
88
+ break;
89
+ case 'history':
90
+ if (counts.task)
91
+ parts.push(`${counts.task} completed task(s)`);
92
+ break;
93
+ case 'qa':
94
+ if (counts.qa)
95
+ parts.push(`${counts.qa} Q&A pair(s)`);
96
+ break;
97
+ }
98
+ if (parts.length === 0) {
99
+ return `No ${queryType} items found.`;
100
+ }
101
+ return `Found ${parts.join(', ')}.`;
102
+ }
103
+ /**
104
+ * Knowledge handlers registry
105
+ */
106
+ export const knowledgeHandlers = {
107
+ query_knowledge: queryKnowledge,
108
+ };