@vibescope/mcp-server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.js +356 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +367 -0
- package/dist/handlers/__test-utils__.d.ts +72 -0
- package/dist/handlers/__test-utils__.js +176 -0
- package/dist/handlers/blockers.d.ts +18 -0
- package/dist/handlers/blockers.js +81 -0
- package/dist/handlers/bodies-of-work.d.ts +34 -0
- package/dist/handlers/bodies-of-work.js +614 -0
- package/dist/handlers/checkouts.d.ts +37 -0
- package/dist/handlers/checkouts.js +377 -0
- package/dist/handlers/cost.d.ts +39 -0
- package/dist/handlers/cost.js +247 -0
- package/dist/handlers/decisions.d.ts +16 -0
- package/dist/handlers/decisions.js +64 -0
- package/dist/handlers/deployment.d.ts +36 -0
- package/dist/handlers/deployment.js +1062 -0
- package/dist/handlers/discovery.d.ts +14 -0
- package/dist/handlers/discovery.js +870 -0
- package/dist/handlers/fallback.d.ts +18 -0
- package/dist/handlers/fallback.js +216 -0
- package/dist/handlers/findings.d.ts +18 -0
- package/dist/handlers/findings.js +110 -0
- package/dist/handlers/git-issues.d.ts +22 -0
- package/dist/handlers/git-issues.js +247 -0
- package/dist/handlers/ideas.d.ts +19 -0
- package/dist/handlers/ideas.js +188 -0
- package/dist/handlers/index.d.ts +29 -0
- package/dist/handlers/index.js +65 -0
- package/dist/handlers/knowledge-query.d.ts +22 -0
- package/dist/handlers/knowledge-query.js +253 -0
- package/dist/handlers/knowledge.d.ts +12 -0
- package/dist/handlers/knowledge.js +108 -0
- package/dist/handlers/milestones.d.ts +20 -0
- package/dist/handlers/milestones.js +179 -0
- package/dist/handlers/organizations.d.ts +36 -0
- package/dist/handlers/organizations.js +428 -0
- package/dist/handlers/progress.d.ts +14 -0
- package/dist/handlers/progress.js +149 -0
- package/dist/handlers/project.d.ts +20 -0
- package/dist/handlers/project.js +278 -0
- package/dist/handlers/requests.d.ts +16 -0
- package/dist/handlers/requests.js +131 -0
- package/dist/handlers/roles.d.ts +30 -0
- package/dist/handlers/roles.js +281 -0
- package/dist/handlers/session.d.ts +20 -0
- package/dist/handlers/session.js +791 -0
- package/dist/handlers/tasks.d.ts +52 -0
- package/dist/handlers/tasks.js +1111 -0
- package/dist/handlers/tasks.test.d.ts +1 -0
- package/dist/handlers/tasks.test.js +431 -0
- package/dist/handlers/types.d.ts +94 -0
- package/dist/handlers/types.js +1 -0
- package/dist/handlers/validation.d.ts +16 -0
- package/dist/handlers/validation.js +188 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2707 -0
- package/dist/knowledge.d.ts +6 -0
- package/dist/knowledge.js +121 -0
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +2498 -0
- package/dist/utils.d.ts +149 -0
- package/dist/utils.js +317 -0
- package/dist/utils.test.d.ts +1 -0
- package/dist/utils.test.js +532 -0
- package/dist/validators.d.ts +35 -0
- package/dist/validators.js +111 -0
- package/dist/validators.test.d.ts +1 -0
- package/dist/validators.test.js +176 -0
- package/package.json +44 -0
- package/src/cli.test.ts +442 -0
- package/src/cli.ts +439 -0
- package/src/handlers/__test-utils__.ts +217 -0
- package/src/handlers/blockers.test.ts +390 -0
- package/src/handlers/blockers.ts +110 -0
- package/src/handlers/bodies-of-work.test.ts +1276 -0
- package/src/handlers/bodies-of-work.ts +783 -0
- package/src/handlers/cost.test.ts +436 -0
- package/src/handlers/cost.ts +322 -0
- package/src/handlers/decisions.test.ts +401 -0
- package/src/handlers/decisions.ts +86 -0
- package/src/handlers/deployment.test.ts +516 -0
- package/src/handlers/deployment.ts +1289 -0
- package/src/handlers/discovery.test.ts +254 -0
- package/src/handlers/discovery.ts +969 -0
- package/src/handlers/fallback.test.ts +687 -0
- package/src/handlers/fallback.ts +260 -0
- package/src/handlers/findings.test.ts +565 -0
- package/src/handlers/findings.ts +153 -0
- package/src/handlers/ideas.test.ts +753 -0
- package/src/handlers/ideas.ts +247 -0
- package/src/handlers/index.ts +69 -0
- package/src/handlers/milestones.test.ts +584 -0
- package/src/handlers/milestones.ts +217 -0
- package/src/handlers/organizations.test.ts +997 -0
- package/src/handlers/organizations.ts +550 -0
- package/src/handlers/progress.test.ts +369 -0
- package/src/handlers/progress.ts +188 -0
- package/src/handlers/project.test.ts +562 -0
- package/src/handlers/project.ts +352 -0
- package/src/handlers/requests.test.ts +531 -0
- package/src/handlers/requests.ts +150 -0
- package/src/handlers/session.test.ts +459 -0
- package/src/handlers/session.ts +912 -0
- package/src/handlers/tasks.test.ts +602 -0
- package/src/handlers/tasks.ts +1393 -0
- package/src/handlers/types.ts +88 -0
- package/src/handlers/validation.test.ts +880 -0
- package/src/handlers/validation.ts +223 -0
- package/src/index.ts +3205 -0
- package/src/knowledge.ts +132 -0
- package/src/tmpclaude-0078-cwd +1 -0
- package/src/tmpclaude-0ee1-cwd +1 -0
- package/src/tmpclaude-2dd5-cwd +1 -0
- package/src/tmpclaude-344c-cwd +1 -0
- package/src/tmpclaude-3860-cwd +1 -0
- package/src/tmpclaude-4b63-cwd +1 -0
- package/src/tmpclaude-5c73-cwd +1 -0
- package/src/tmpclaude-5ee3-cwd +1 -0
- package/src/tmpclaude-6795-cwd +1 -0
- package/src/tmpclaude-709e-cwd +1 -0
- package/src/tmpclaude-9839-cwd +1 -0
- package/src/tmpclaude-d829-cwd +1 -0
- package/src/tmpclaude-e072-cwd +1 -0
- package/src/tmpclaude-f6ee-cwd +1 -0
- package/src/utils.test.ts +681 -0
- package/src/utils.ts +375 -0
- package/src/validators.test.ts +223 -0
- package/src/validators.ts +122 -0
- package/tmpclaude-0439-cwd +1 -0
- package/tmpclaude-132f-cwd +1 -0
- package/tmpclaude-15bb-cwd +1 -0
- package/tmpclaude-165a-cwd +1 -0
- package/tmpclaude-1ba9-cwd +1 -0
- package/tmpclaude-21a3-cwd +1 -0
- package/tmpclaude-2a38-cwd +1 -0
- package/tmpclaude-2adf-cwd +1 -0
- package/tmpclaude-2f56-cwd +1 -0
- package/tmpclaude-3626-cwd +1 -0
- package/tmpclaude-3727-cwd +1 -0
- package/tmpclaude-40bc-cwd +1 -0
- package/tmpclaude-436f-cwd +1 -0
- package/tmpclaude-4783-cwd +1 -0
- package/tmpclaude-4b6d-cwd +1 -0
- package/tmpclaude-4ba4-cwd +1 -0
- package/tmpclaude-51e6-cwd +1 -0
- package/tmpclaude-5ecf-cwd +1 -0
- package/tmpclaude-6f97-cwd +1 -0
- package/tmpclaude-7fb2-cwd +1 -0
- package/tmpclaude-825c-cwd +1 -0
- package/tmpclaude-8baf-cwd +1 -0
- package/tmpclaude-8d9f-cwd +1 -0
- package/tmpclaude-975c-cwd +1 -0
- package/tmpclaude-9983-cwd +1 -0
- package/tmpclaude-a045-cwd +1 -0
- package/tmpclaude-ac4a-cwd +1 -0
- package/tmpclaude-b593-cwd +1 -0
- package/tmpclaude-b891-cwd +1 -0
- package/tmpclaude-c032-cwd +1 -0
- package/tmpclaude-cf43-cwd +1 -0
- package/tmpclaude-d040-cwd +1 -0
- package/tmpclaude-dcdd-cwd +1 -0
- package/tmpclaude-dcee-cwd +1 -0
- package/tmpclaude-e16b-cwd +1 -0
- package/tmpclaude-ecd2-cwd +1 -0
- package/tmpclaude-f48d-cwd +1 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,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
|
+
};
|