@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.
- package/README.md +113 -98
- package/dist/api-client.d.ts +1114 -0
- package/dist/api-client.js +698 -0
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +39 -240
- package/dist/config/tool-categories.d.ts +31 -0
- package/dist/config/tool-categories.js +253 -0
- package/dist/handlers/blockers.js +57 -58
- package/dist/handlers/bodies-of-work.d.ts +2 -0
- package/dist/handlers/bodies-of-work.js +106 -476
- package/dist/handlers/cost.d.ts +1 -0
- package/dist/handlers/cost.js +35 -113
- package/dist/handlers/decisions.d.ts +2 -0
- package/dist/handlers/decisions.js +28 -27
- package/dist/handlers/deployment.js +112 -828
- package/dist/handlers/discovery.js +31 -0
- package/dist/handlers/fallback.d.ts +2 -0
- package/dist/handlers/fallback.js +39 -134
- package/dist/handlers/findings.js +43 -67
- package/dist/handlers/git-issues.d.ts +9 -13
- package/dist/handlers/git-issues.js +80 -225
- package/dist/handlers/ideas.d.ts +3 -0
- package/dist/handlers/ideas.js +53 -134
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/milestones.d.ts +2 -0
- package/dist/handlers/milestones.js +51 -98
- package/dist/handlers/organizations.js +79 -275
- package/dist/handlers/progress.d.ts +2 -0
- package/dist/handlers/progress.js +25 -123
- package/dist/handlers/project.js +42 -221
- package/dist/handlers/requests.d.ts +2 -0
- package/dist/handlers/requests.js +23 -83
- package/dist/handlers/session.js +99 -585
- package/dist/handlers/sprints.d.ts +32 -0
- package/dist/handlers/sprints.js +274 -0
- package/dist/handlers/tasks.d.ts +7 -10
- package/dist/handlers/tasks.js +230 -900
- package/dist/handlers/tool-docs.d.ts +8 -0
- package/dist/handlers/tool-docs.js +657 -0
- package/dist/handlers/types.d.ts +11 -3
- package/dist/handlers/validation.d.ts +1 -1
- package/dist/handlers/validation.js +26 -153
- package/dist/index.js +473 -160
- package/dist/knowledge.js +106 -9
- package/dist/tools.js +4 -0
- package/dist/validators.d.ts +21 -0
- package/dist/validators.js +91 -0
- package/package.json +2 -3
- package/src/api-client.ts +1752 -0
- package/src/cli.test.ts +128 -302
- package/src/cli.ts +41 -285
- package/src/handlers/__test-setup__.ts +210 -0
- package/src/handlers/__test-utils__.ts +4 -134
- package/src/handlers/blockers.test.ts +114 -124
- package/src/handlers/blockers.ts +68 -70
- package/src/handlers/bodies-of-work.test.ts +236 -831
- package/src/handlers/bodies-of-work.ts +194 -525
- package/src/handlers/cost.test.ts +149 -113
- package/src/handlers/cost.ts +44 -132
- package/src/handlers/decisions.test.ts +111 -209
- package/src/handlers/decisions.ts +35 -27
- package/src/handlers/deployment.test.ts +193 -239
- package/src/handlers/deployment.ts +140 -895
- package/src/handlers/discovery.test.ts +20 -67
- package/src/handlers/discovery.ts +32 -0
- package/src/handlers/fallback.test.ts +128 -361
- package/src/handlers/fallback.ts +62 -148
- package/src/handlers/findings.test.ts +127 -345
- package/src/handlers/findings.ts +49 -66
- package/src/handlers/git-issues.test.ts +623 -0
- package/src/handlers/git-issues.ts +174 -0
- package/src/handlers/ideas.test.ts +229 -343
- package/src/handlers/ideas.ts +69 -143
- package/src/handlers/index.ts +6 -0
- package/src/handlers/milestones.test.ts +167 -281
- package/src/handlers/milestones.ts +54 -93
- package/src/handlers/organizations.test.ts +275 -467
- package/src/handlers/organizations.ts +84 -294
- package/src/handlers/progress.test.ts +112 -218
- package/src/handlers/progress.ts +29 -142
- package/src/handlers/project.test.ts +203 -226
- package/src/handlers/project.ts +48 -238
- package/src/handlers/requests.test.ts +74 -342
- package/src/handlers/requests.ts +25 -83
- package/src/handlers/session.test.ts +241 -206
- package/src/handlers/session.ts +110 -657
- package/src/handlers/sprints.test.ts +711 -0
- package/src/handlers/sprints.ts +497 -0
- package/src/handlers/tasks.test.ts +608 -353
- package/src/handlers/tasks.ts +248 -1025
- package/src/handlers/types.ts +12 -4
- package/src/handlers/validation.test.ts +189 -572
- package/src/handlers/validation.ts +29 -166
- package/src/index.ts +473 -184
- package/src/knowledge.ts +107 -9
- package/src/tools.ts +2506 -0
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +127 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +14 -13
- package/dist/cli.test.d.ts +0 -1
- package/dist/cli.test.js +0 -367
- package/dist/handlers/__test-utils__.d.ts +0 -72
- package/dist/handlers/__test-utils__.js +0 -176
- package/dist/handlers/checkouts.d.ts +0 -37
- package/dist/handlers/checkouts.js +0 -377
- package/dist/handlers/knowledge-query.d.ts +0 -22
- package/dist/handlers/knowledge-query.js +0 -253
- package/dist/handlers/knowledge.d.ts +0 -12
- package/dist/handlers/knowledge.js +0 -108
- package/dist/handlers/roles.d.ts +0 -30
- package/dist/handlers/roles.js +0 -281
- package/dist/handlers/tasks.test.d.ts +0 -1
- package/dist/handlers/tasks.test.js +0 -431
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -532
- package/dist/validators.test.d.ts +0 -1
- package/dist/validators.test.js +0 -176
- package/src/tmpclaude-0078-cwd +0 -1
- package/src/tmpclaude-0ee1-cwd +0 -1
- package/src/tmpclaude-2dd5-cwd +0 -1
- package/src/tmpclaude-344c-cwd +0 -1
- package/src/tmpclaude-3860-cwd +0 -1
- package/src/tmpclaude-4b63-cwd +0 -1
- package/src/tmpclaude-5c73-cwd +0 -1
- package/src/tmpclaude-5ee3-cwd +0 -1
- package/src/tmpclaude-6795-cwd +0 -1
- package/src/tmpclaude-709e-cwd +0 -1
- package/src/tmpclaude-9839-cwd +0 -1
- package/src/tmpclaude-d829-cwd +0 -1
- package/src/tmpclaude-e072-cwd +0 -1
- package/src/tmpclaude-f6ee-cwd +0 -1
- package/tmpclaude-0439-cwd +0 -1
- package/tmpclaude-132f-cwd +0 -1
- package/tmpclaude-15bb-cwd +0 -1
- package/tmpclaude-165a-cwd +0 -1
- package/tmpclaude-1ba9-cwd +0 -1
- package/tmpclaude-21a3-cwd +0 -1
- package/tmpclaude-2a38-cwd +0 -1
- package/tmpclaude-2adf-cwd +0 -1
- package/tmpclaude-2f56-cwd +0 -1
- package/tmpclaude-3626-cwd +0 -1
- package/tmpclaude-3727-cwd +0 -1
- package/tmpclaude-40bc-cwd +0 -1
- package/tmpclaude-436f-cwd +0 -1
- package/tmpclaude-4783-cwd +0 -1
- package/tmpclaude-4b6d-cwd +0 -1
- package/tmpclaude-4ba4-cwd +0 -1
- package/tmpclaude-51e6-cwd +0 -1
- package/tmpclaude-5ecf-cwd +0 -1
- package/tmpclaude-6f97-cwd +0 -1
- package/tmpclaude-7fb2-cwd +0 -1
- package/tmpclaude-825c-cwd +0 -1
- package/tmpclaude-8baf-cwd +0 -1
- package/tmpclaude-8d9f-cwd +0 -1
- package/tmpclaude-975c-cwd +0 -1
- package/tmpclaude-9983-cwd +0 -1
- package/tmpclaude-a045-cwd +0 -1
- package/tmpclaude-ac4a-cwd +0 -1
- package/tmpclaude-b593-cwd +0 -1
- package/tmpclaude-b891-cwd +0 -1
- package/tmpclaude-c032-cwd +0 -1
- package/tmpclaude-cf43-cwd +0 -1
- package/tmpclaude-d040-cwd +0 -1
- package/tmpclaude-dcdd-cwd +0 -1
- package/tmpclaude-dcee-cwd +0 -1
- package/tmpclaude-e16b-cwd +0 -1
- package/tmpclaude-ecd2-cwd +0 -1
- package/tmpclaude-f48d-cwd +0 -1
|
@@ -14,10 +14,13 @@
|
|
|
14
14
|
* - remove_task_dependency
|
|
15
15
|
* - get_task_dependencies
|
|
16
16
|
* - get_next_body_of_work_task
|
|
17
|
+
*
|
|
18
|
+
* MIGRATED: Uses Vibescope API client instead of direct Supabase
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
20
22
|
import { validateRequired, validateUUID } from '../validators.js';
|
|
23
|
+
import { getApiClient } from '../api-client.js';
|
|
21
24
|
|
|
22
25
|
type BodyOfWorkStatus = 'draft' | 'active' | 'completed' | 'cancelled';
|
|
23
26
|
type TaskPhase = 'pre' | 'core' | 'post';
|
|
@@ -48,30 +51,34 @@ export const createBodyOfWork: Handler = async (args, ctx) => {
|
|
|
48
51
|
validateUUID(project_id, 'project_id');
|
|
49
52
|
validateRequired(title, 'title');
|
|
50
53
|
|
|
51
|
-
const {
|
|
54
|
+
const { session } = ctx;
|
|
55
|
+
const apiClient = getApiClient();
|
|
52
56
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
const response = await apiClient.proxy<{
|
|
58
|
+
success: boolean;
|
|
59
|
+
body_of_work_id: string;
|
|
60
|
+
}>('create_body_of_work', {
|
|
61
|
+
project_id,
|
|
62
|
+
title,
|
|
63
|
+
description,
|
|
64
|
+
auto_deploy_on_completion,
|
|
65
|
+
deploy_environment,
|
|
66
|
+
deploy_version_bump,
|
|
67
|
+
deploy_trigger
|
|
68
|
+
}, {
|
|
69
|
+
session_id: session.currentSessionId,
|
|
70
|
+
persona: session.currentPersona,
|
|
71
|
+
instance_id: session.instanceId
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new Error(`Failed to create body of work: ${response.error}`);
|
|
76
|
+
}
|
|
70
77
|
|
|
71
78
|
return {
|
|
72
79
|
result: {
|
|
73
80
|
success: true,
|
|
74
|
-
body_of_work_id: data
|
|
81
|
+
body_of_work_id: response.data?.body_of_work_id,
|
|
75
82
|
title,
|
|
76
83
|
status: 'draft',
|
|
77
84
|
message: 'Body of work created. Add tasks with add_task_to_body_of_work, then activate with activate_body_of_work.',
|
|
@@ -101,27 +108,27 @@ export const updateBodyOfWork: Handler = async (args, ctx) => {
|
|
|
101
108
|
validateRequired(body_of_work_id, 'body_of_work_id');
|
|
102
109
|
validateUUID(body_of_work_id, 'body_of_work_id');
|
|
103
110
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const updates: Record<string, unknown> = {};
|
|
108
|
-
if (title !== undefined) updates.title = title;
|
|
109
|
-
if (description !== undefined) updates.description = description;
|
|
110
|
-
if (auto_deploy_on_completion !== undefined) updates.auto_deploy_on_completion = auto_deploy_on_completion;
|
|
111
|
-
if (deploy_environment !== undefined) updates.deploy_environment = deploy_environment;
|
|
112
|
-
if (deploy_version_bump !== undefined) updates.deploy_version_bump = deploy_version_bump;
|
|
113
|
-
if (deploy_trigger !== undefined) updates.deploy_trigger = deploy_trigger;
|
|
114
|
-
|
|
115
|
-
if (Object.keys(updates).length === 0) {
|
|
111
|
+
// Check if any updates provided
|
|
112
|
+
if (title === undefined && description === undefined && auto_deploy_on_completion === undefined &&
|
|
113
|
+
deploy_environment === undefined && deploy_version_bump === undefined && deploy_trigger === undefined) {
|
|
116
114
|
return { result: { success: true, message: 'No updates provided' } };
|
|
117
115
|
}
|
|
118
116
|
|
|
119
|
-
const
|
|
120
|
-
.from('bodies_of_work')
|
|
121
|
-
.update(updates)
|
|
122
|
-
.eq('id', body_of_work_id);
|
|
117
|
+
const apiClient = getApiClient();
|
|
123
118
|
|
|
124
|
-
|
|
119
|
+
const response = await apiClient.proxy<{ success: boolean }>('update_body_of_work', {
|
|
120
|
+
body_of_work_id,
|
|
121
|
+
title,
|
|
122
|
+
description,
|
|
123
|
+
auto_deploy_on_completion,
|
|
124
|
+
deploy_environment,
|
|
125
|
+
deploy_version_bump,
|
|
126
|
+
deploy_trigger
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
throw new Error(`Failed to update body of work: ${response.error}`);
|
|
131
|
+
}
|
|
125
132
|
|
|
126
133
|
return { result: { success: true, body_of_work_id } };
|
|
127
134
|
};
|
|
@@ -132,154 +139,69 @@ export const getBodyOfWork: Handler = async (args, ctx) => {
|
|
|
132
139
|
validateRequired(body_of_work_id, 'body_of_work_id');
|
|
133
140
|
validateUUID(body_of_work_id, 'body_of_work_id');
|
|
134
141
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
// Get body of work
|
|
138
|
-
const { data: bow, error: bowError } = await supabase
|
|
139
|
-
.from('bodies_of_work')
|
|
140
|
-
.select('*')
|
|
141
|
-
.eq('id', body_of_work_id)
|
|
142
|
-
.single();
|
|
143
|
-
|
|
144
|
-
if (bowError || !bow) {
|
|
145
|
-
throw new Error(`Body of work not found: ${body_of_work_id}`);
|
|
146
|
-
}
|
|
142
|
+
const apiClient = getApiClient();
|
|
147
143
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
`)
|
|
162
|
-
.eq('body_of_work_id', body_of_work_id)
|
|
163
|
-
.order('order_index');
|
|
164
|
-
|
|
165
|
-
if (taskError) throw new Error(`Failed to fetch tasks: ${taskError.message}`);
|
|
166
|
-
|
|
167
|
-
// Organize tasks by phase
|
|
168
|
-
const preTasks: unknown[] = [];
|
|
169
|
-
const coreTasks: unknown[] = [];
|
|
170
|
-
const postTasks: unknown[] = [];
|
|
171
|
-
|
|
172
|
-
for (const link of taskLinks || []) {
|
|
173
|
-
const task = link.tasks;
|
|
174
|
-
if (!task) continue;
|
|
175
|
-
|
|
176
|
-
const taskWithPhase = { ...task, order_index: link.order_index };
|
|
177
|
-
|
|
178
|
-
switch (link.phase) {
|
|
179
|
-
case 'pre':
|
|
180
|
-
preTasks.push(taskWithPhase);
|
|
181
|
-
break;
|
|
182
|
-
case 'core':
|
|
183
|
-
coreTasks.push(taskWithPhase);
|
|
184
|
-
break;
|
|
185
|
-
case 'post':
|
|
186
|
-
postTasks.push(taskWithPhase);
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
144
|
+
const response = await apiClient.proxy<{
|
|
145
|
+
id: string;
|
|
146
|
+
title: string;
|
|
147
|
+
description?: string;
|
|
148
|
+
status: string;
|
|
149
|
+
progress_percentage: number;
|
|
150
|
+
pre_tasks: unknown[];
|
|
151
|
+
core_tasks: unknown[];
|
|
152
|
+
post_tasks: unknown[];
|
|
153
|
+
total_tasks: number;
|
|
154
|
+
}>('get_body_of_work', { body_of_work_id });
|
|
155
|
+
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
throw new Error(`Failed to get body of work: ${response.error}`);
|
|
189
158
|
}
|
|
190
159
|
|
|
191
|
-
return {
|
|
192
|
-
result: {
|
|
193
|
-
...bow,
|
|
194
|
-
pre_tasks: preTasks,
|
|
195
|
-
core_tasks: coreTasks,
|
|
196
|
-
post_tasks: postTasks,
|
|
197
|
-
total_tasks: preTasks.length + coreTasks.length + postTasks.length,
|
|
198
|
-
},
|
|
199
|
-
};
|
|
160
|
+
return { result: response.data };
|
|
200
161
|
};
|
|
201
162
|
|
|
202
163
|
export const getBodiesOfWork: Handler = async (args, ctx) => {
|
|
203
|
-
const { project_id, status } = args as {
|
|
164
|
+
const { project_id, status, limit = 50, offset = 0, search_query } = args as {
|
|
204
165
|
project_id: string;
|
|
205
166
|
status?: BodyOfWorkStatus;
|
|
167
|
+
limit?: number;
|
|
168
|
+
offset?: number;
|
|
169
|
+
search_query?: string;
|
|
206
170
|
};
|
|
207
171
|
|
|
208
172
|
validateRequired(project_id, 'project_id');
|
|
209
173
|
validateUUID(project_id, 'project_id');
|
|
210
174
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (error) throw new Error(`Failed to fetch bodies of work: ${error.message}`);
|
|
237
|
-
|
|
238
|
-
const bodies = data || [];
|
|
239
|
-
if (bodies.length === 0) {
|
|
240
|
-
return { result: { bodies_of_work: [] } };
|
|
241
|
-
}
|
|
175
|
+
const apiClient = getApiClient();
|
|
176
|
+
|
|
177
|
+
const response = await apiClient.proxy<{
|
|
178
|
+
bodies_of_work: Array<{
|
|
179
|
+
id: string;
|
|
180
|
+
title: string;
|
|
181
|
+
description?: string;
|
|
182
|
+
status: string;
|
|
183
|
+
progress_percentage: number;
|
|
184
|
+
task_counts: {
|
|
185
|
+
pre: { total: number; completed: number };
|
|
186
|
+
core: { total: number; completed: number };
|
|
187
|
+
post: { total: number; completed: number };
|
|
188
|
+
};
|
|
189
|
+
}>;
|
|
190
|
+
total_count: number;
|
|
191
|
+
has_more: boolean;
|
|
192
|
+
}>('get_bodies_of_work', {
|
|
193
|
+
project_id,
|
|
194
|
+
status,
|
|
195
|
+
limit: Math.min(limit, 100),
|
|
196
|
+
offset,
|
|
197
|
+
search_query
|
|
198
|
+
});
|
|
242
199
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const { data: allTaskLinks } = await supabase
|
|
246
|
-
.from('body_of_work_tasks')
|
|
247
|
-
.select('body_of_work_id, phase, tasks!inner(status)')
|
|
248
|
-
.in('body_of_work_id', bodyIds);
|
|
249
|
-
|
|
250
|
-
// Group task links by body_of_work_id
|
|
251
|
-
const taskLinksByBody = new Map<string, typeof allTaskLinks>();
|
|
252
|
-
for (const link of allTaskLinks || []) {
|
|
253
|
-
const existing = taskLinksByBody.get(link.body_of_work_id) || [];
|
|
254
|
-
existing.push(link);
|
|
255
|
-
taskLinksByBody.set(link.body_of_work_id, existing);
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
throw new Error(`Failed to fetch bodies of work: ${response.error}`);
|
|
256
202
|
}
|
|
257
203
|
|
|
258
|
-
|
|
259
|
-
const bodiesWithCounts = bodies.map((bow) => {
|
|
260
|
-
const taskLinks = taskLinksByBody.get(bow.id) || [];
|
|
261
|
-
const taskCounts = {
|
|
262
|
-
pre: { total: 0, completed: 0 },
|
|
263
|
-
core: { total: 0, completed: 0 },
|
|
264
|
-
post: { total: 0, completed: 0 },
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
for (const link of taskLinks) {
|
|
268
|
-
const phase = link.phase as TaskPhase;
|
|
269
|
-
taskCounts[phase].total++;
|
|
270
|
-
const taskData = link.tasks as unknown as { status: string } | null;
|
|
271
|
-
if (taskData?.status === 'completed') {
|
|
272
|
-
taskCounts[phase].completed++;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return {
|
|
277
|
-
...bow,
|
|
278
|
-
task_counts: taskCounts,
|
|
279
|
-
};
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
return { result: { bodies_of_work: bodiesWithCounts } };
|
|
204
|
+
return { result: response.data };
|
|
283
205
|
};
|
|
284
206
|
|
|
285
207
|
export const deleteBodyOfWork: Handler = async (args, ctx) => {
|
|
@@ -288,12 +210,15 @@ export const deleteBodyOfWork: Handler = async (args, ctx) => {
|
|
|
288
210
|
validateRequired(body_of_work_id, 'body_of_work_id');
|
|
289
211
|
validateUUID(body_of_work_id, 'body_of_work_id');
|
|
290
212
|
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
213
|
+
const apiClient = getApiClient();
|
|
214
|
+
|
|
215
|
+
const response = await apiClient.proxy<{ success: boolean }>('delete_body_of_work', {
|
|
216
|
+
body_of_work_id
|
|
217
|
+
});
|
|
295
218
|
|
|
296
|
-
if (
|
|
219
|
+
if (!response.ok) {
|
|
220
|
+
throw new Error(`Failed to delete body of work: ${response.error}`);
|
|
221
|
+
}
|
|
297
222
|
|
|
298
223
|
return { result: { success: true, message: 'Body of work deleted. Tasks are preserved.' } };
|
|
299
224
|
};
|
|
@@ -311,70 +236,26 @@ export const addTaskToBodyOfWork: Handler = async (args, ctx) => {
|
|
|
311
236
|
validateRequired(task_id, 'task_id');
|
|
312
237
|
validateUUID(task_id, 'task_id');
|
|
313
238
|
|
|
314
|
-
const
|
|
315
|
-
const taskPhase = phase || 'core';
|
|
239
|
+
const apiClient = getApiClient();
|
|
316
240
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
throw new Error(`Cannot add tasks to ${bow.status} body of work`);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Check if task is already in a body of work
|
|
333
|
-
const { data: existingLink } = await supabase
|
|
334
|
-
.from('body_of_work_tasks')
|
|
335
|
-
.select('body_of_work_id')
|
|
336
|
-
.eq('task_id', task_id)
|
|
337
|
-
.single();
|
|
338
|
-
|
|
339
|
-
if (existingLink) {
|
|
340
|
-
throw new Error('Task is already assigned to a body of work. Remove it first.');
|
|
341
|
-
}
|
|
241
|
+
const response = await apiClient.proxy<{
|
|
242
|
+
success: boolean;
|
|
243
|
+
body_of_work_id: string;
|
|
244
|
+
task_id: string;
|
|
245
|
+
phase: string;
|
|
246
|
+
order_index: number;
|
|
247
|
+
}>('add_task_to_body_of_work', {
|
|
248
|
+
body_of_work_id,
|
|
249
|
+
task_id,
|
|
250
|
+
phase: phase || 'core',
|
|
251
|
+
order_index
|
|
252
|
+
});
|
|
342
253
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (finalOrderIndex === undefined) {
|
|
346
|
-
const { data: maxOrder } = await supabase
|
|
347
|
-
.from('body_of_work_tasks')
|
|
348
|
-
.select('order_index')
|
|
349
|
-
.eq('body_of_work_id', body_of_work_id)
|
|
350
|
-
.eq('phase', taskPhase)
|
|
351
|
-
.order('order_index', { ascending: false })
|
|
352
|
-
.limit(1)
|
|
353
|
-
.single();
|
|
354
|
-
|
|
355
|
-
finalOrderIndex = maxOrder ? maxOrder.order_index + 1 : 0;
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
throw new Error(`Failed to add task to body of work: ${response.error}`);
|
|
356
256
|
}
|
|
357
257
|
|
|
358
|
-
|
|
359
|
-
.from('body_of_work_tasks')
|
|
360
|
-
.insert({
|
|
361
|
-
body_of_work_id,
|
|
362
|
-
task_id,
|
|
363
|
-
phase: taskPhase,
|
|
364
|
-
order_index: finalOrderIndex,
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
if (error) throw new Error(`Failed to add task to body of work: ${error.message}`);
|
|
368
|
-
|
|
369
|
-
return {
|
|
370
|
-
result: {
|
|
371
|
-
success: true,
|
|
372
|
-
body_of_work_id,
|
|
373
|
-
task_id,
|
|
374
|
-
phase: taskPhase,
|
|
375
|
-
order_index: finalOrderIndex,
|
|
376
|
-
},
|
|
377
|
-
};
|
|
258
|
+
return { result: response.data };
|
|
378
259
|
};
|
|
379
260
|
|
|
380
261
|
export const removeTaskFromBodyOfWork: Handler = async (args, ctx) => {
|
|
@@ -383,38 +264,19 @@ export const removeTaskFromBodyOfWork: Handler = async (args, ctx) => {
|
|
|
383
264
|
validateRequired(task_id, 'task_id');
|
|
384
265
|
validateUUID(task_id, 'task_id');
|
|
385
266
|
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
// Check if link exists
|
|
389
|
-
const { data: link } = await supabase
|
|
390
|
-
.from('body_of_work_tasks')
|
|
391
|
-
.select('body_of_work_id')
|
|
392
|
-
.eq('task_id', task_id)
|
|
393
|
-
.single();
|
|
394
|
-
|
|
395
|
-
if (!link) {
|
|
396
|
-
return { result: { success: true, message: 'Task is not in any body of work' } };
|
|
397
|
-
}
|
|
267
|
+
const apiClient = getApiClient();
|
|
398
268
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
.single();
|
|
269
|
+
const response = await apiClient.proxy<{
|
|
270
|
+
success: boolean;
|
|
271
|
+
body_of_work_id?: string;
|
|
272
|
+
message?: string;
|
|
273
|
+
}>('remove_task_from_body_of_work', { task_id });
|
|
405
274
|
|
|
406
|
-
if (
|
|
407
|
-
throw new Error(
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
throw new Error(`Failed to remove task from body of work: ${response.error}`);
|
|
408
277
|
}
|
|
409
278
|
|
|
410
|
-
|
|
411
|
-
.from('body_of_work_tasks')
|
|
412
|
-
.delete()
|
|
413
|
-
.eq('task_id', task_id);
|
|
414
|
-
|
|
415
|
-
if (error) throw new Error(`Failed to remove task from body of work: ${error.message}`);
|
|
416
|
-
|
|
417
|
-
return { result: { success: true, body_of_work_id: link.body_of_work_id } };
|
|
279
|
+
return { result: response.data };
|
|
418
280
|
};
|
|
419
281
|
|
|
420
282
|
export const activateBodyOfWork: Handler = async (args, ctx) => {
|
|
@@ -423,52 +285,21 @@ export const activateBodyOfWork: Handler = async (args, ctx) => {
|
|
|
423
285
|
validateRequired(body_of_work_id, 'body_of_work_id');
|
|
424
286
|
validateUUID(body_of_work_id, 'body_of_work_id');
|
|
425
287
|
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
// Check current status
|
|
429
|
-
const { data: bow, error: bowError } = await supabase
|
|
430
|
-
.from('bodies_of_work')
|
|
431
|
-
.select('status, title')
|
|
432
|
-
.eq('id', body_of_work_id)
|
|
433
|
-
.single();
|
|
288
|
+
const apiClient = getApiClient();
|
|
434
289
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Check if there are any tasks
|
|
444
|
-
const { count } = await supabase
|
|
445
|
-
.from('body_of_work_tasks')
|
|
446
|
-
.select('id', { count: 'exact', head: true })
|
|
447
|
-
.eq('body_of_work_id', body_of_work_id);
|
|
290
|
+
const response = await apiClient.proxy<{
|
|
291
|
+
success: boolean;
|
|
292
|
+
body_of_work_id: string;
|
|
293
|
+
title: string;
|
|
294
|
+
status: string;
|
|
295
|
+
message: string;
|
|
296
|
+
}>('activate_body_of_work', { body_of_work_id });
|
|
448
297
|
|
|
449
|
-
if (!
|
|
450
|
-
throw new Error(
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
throw new Error(`Failed to activate body of work: ${response.error}`);
|
|
451
300
|
}
|
|
452
301
|
|
|
453
|
-
|
|
454
|
-
.from('bodies_of_work')
|
|
455
|
-
.update({
|
|
456
|
-
status: 'active',
|
|
457
|
-
activated_at: new Date().toISOString(),
|
|
458
|
-
})
|
|
459
|
-
.eq('id', body_of_work_id);
|
|
460
|
-
|
|
461
|
-
if (error) throw new Error(`Failed to activate body of work: ${error.message}`);
|
|
462
|
-
|
|
463
|
-
return {
|
|
464
|
-
result: {
|
|
465
|
-
success: true,
|
|
466
|
-
body_of_work_id,
|
|
467
|
-
title: bow.title,
|
|
468
|
-
status: 'active',
|
|
469
|
-
message: 'Body of work activated. Tasks can now be worked on following phase order.',
|
|
470
|
-
},
|
|
471
|
-
};
|
|
302
|
+
return { result: response.data };
|
|
472
303
|
};
|
|
473
304
|
|
|
474
305
|
export const addTaskDependency: Handler = async (args, ctx) => {
|
|
@@ -489,70 +320,25 @@ export const addTaskDependency: Handler = async (args, ctx) => {
|
|
|
489
320
|
throw new Error('A task cannot depend on itself');
|
|
490
321
|
}
|
|
491
322
|
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
// Verify both tasks belong to the same body of work
|
|
495
|
-
const { data: taskLinks, error: taskError } = await supabase
|
|
496
|
-
.from('body_of_work_tasks')
|
|
497
|
-
.select('task_id')
|
|
498
|
-
.eq('body_of_work_id', body_of_work_id)
|
|
499
|
-
.in('task_id', [task_id, depends_on_task_id]);
|
|
500
|
-
|
|
501
|
-
if (taskError) throw new Error(`Failed to verify tasks: ${taskError.message}`);
|
|
502
|
-
|
|
503
|
-
if (!taskLinks || taskLinks.length !== 2) {
|
|
504
|
-
throw new Error('Both tasks must belong to the specified body of work');
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Check for circular dependencies by traversing the dependency graph
|
|
508
|
-
const visited = new Set<string>();
|
|
509
|
-
const checkCircular = async (currentTaskId: string): Promise<boolean> => {
|
|
510
|
-
if (currentTaskId === task_id) return true; // Found cycle
|
|
511
|
-
if (visited.has(currentTaskId)) return false;
|
|
512
|
-
visited.add(currentTaskId);
|
|
513
|
-
|
|
514
|
-
const { data: deps } = await supabase
|
|
515
|
-
.from('body_of_work_task_dependencies')
|
|
516
|
-
.select('depends_on_task_id')
|
|
517
|
-
.eq('task_id', currentTaskId)
|
|
518
|
-
.eq('body_of_work_id', body_of_work_id);
|
|
519
|
-
|
|
520
|
-
for (const dep of deps || []) {
|
|
521
|
-
if (await checkCircular(dep.depends_on_task_id)) {
|
|
522
|
-
return true;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
return false;
|
|
526
|
-
};
|
|
323
|
+
const apiClient = getApiClient();
|
|
527
324
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
325
|
+
const response = await apiClient.proxy<{
|
|
326
|
+
success: boolean;
|
|
327
|
+
body_of_work_id: string;
|
|
328
|
+
task_id: string;
|
|
329
|
+
depends_on_task_id: string;
|
|
330
|
+
message: string;
|
|
331
|
+
}>('add_task_dependency', {
|
|
332
|
+
body_of_work_id,
|
|
333
|
+
task_id,
|
|
334
|
+
depends_on_task_id
|
|
335
|
+
});
|
|
531
336
|
|
|
532
|
-
|
|
533
|
-
.
|
|
534
|
-
.insert({
|
|
535
|
-
body_of_work_id,
|
|
536
|
-
task_id,
|
|
537
|
-
depends_on_task_id,
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
if (error) {
|
|
541
|
-
if (error.message.includes('duplicate')) {
|
|
542
|
-
throw new Error('This dependency already exists');
|
|
543
|
-
}
|
|
544
|
-
throw new Error(`Failed to add dependency: ${error.message}`);
|
|
337
|
+
if (!response.ok) {
|
|
338
|
+
throw new Error(`Failed to add task dependency: ${response.error}`);
|
|
545
339
|
}
|
|
546
340
|
|
|
547
|
-
return {
|
|
548
|
-
result: {
|
|
549
|
-
success: true,
|
|
550
|
-
body_of_work_id,
|
|
551
|
-
task_id,
|
|
552
|
-
depends_on_task_id,
|
|
553
|
-
message: `Task now depends on completion of the specified task`,
|
|
554
|
-
},
|
|
555
|
-
};
|
|
341
|
+
return { result: response.data };
|
|
556
342
|
};
|
|
557
343
|
|
|
558
344
|
export const removeTaskDependency: Handler = async (args, ctx) => {
|
|
@@ -566,17 +352,22 @@ export const removeTaskDependency: Handler = async (args, ctx) => {
|
|
|
566
352
|
validateRequired(depends_on_task_id, 'depends_on_task_id');
|
|
567
353
|
validateUUID(depends_on_task_id, 'depends_on_task_id');
|
|
568
354
|
|
|
569
|
-
const
|
|
355
|
+
const apiClient = getApiClient();
|
|
570
356
|
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
357
|
+
const response = await apiClient.proxy<{
|
|
358
|
+
success: boolean;
|
|
359
|
+
task_id: string;
|
|
360
|
+
depends_on_task_id: string;
|
|
361
|
+
}>('remove_task_dependency', {
|
|
362
|
+
task_id,
|
|
363
|
+
depends_on_task_id
|
|
364
|
+
});
|
|
576
365
|
|
|
577
|
-
if (
|
|
366
|
+
if (!response.ok) {
|
|
367
|
+
throw new Error(`Failed to remove task dependency: ${response.error}`);
|
|
368
|
+
}
|
|
578
369
|
|
|
579
|
-
return { result:
|
|
370
|
+
return { result: response.data };
|
|
580
371
|
};
|
|
581
372
|
|
|
582
373
|
export const getTaskDependencies: Handler = async (args, ctx) => {
|
|
@@ -592,29 +383,25 @@ export const getTaskDependencies: Handler = async (args, ctx) => {
|
|
|
592
383
|
if (body_of_work_id) validateUUID(body_of_work_id, 'body_of_work_id');
|
|
593
384
|
if (task_id) validateUUID(task_id, 'task_id');
|
|
594
385
|
|
|
595
|
-
const
|
|
386
|
+
const apiClient = getApiClient();
|
|
596
387
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
388
|
+
const response = await apiClient.proxy<{
|
|
389
|
+
dependencies: Array<{
|
|
390
|
+
id: string;
|
|
391
|
+
task_id: string;
|
|
392
|
+
depends_on_task_id: string;
|
|
393
|
+
created_at: string;
|
|
394
|
+
}>;
|
|
395
|
+
}>('get_task_dependencies', {
|
|
396
|
+
body_of_work_id,
|
|
397
|
+
task_id
|
|
398
|
+
});
|
|
605
399
|
|
|
606
|
-
if (
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
if (task_id) {
|
|
610
|
-
query = query.eq('task_id', task_id);
|
|
400
|
+
if (!response.ok) {
|
|
401
|
+
throw new Error(`Failed to fetch task dependencies: ${response.error}`);
|
|
611
402
|
}
|
|
612
403
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (error) throw new Error(`Failed to fetch dependencies: ${error.message}`);
|
|
616
|
-
|
|
617
|
-
return { result: { dependencies: data || [] } };
|
|
404
|
+
return { result: response.data };
|
|
618
405
|
};
|
|
619
406
|
|
|
620
407
|
export const getNextBodyOfWorkTask: Handler = async (args, ctx) => {
|
|
@@ -623,145 +410,27 @@ export const getNextBodyOfWorkTask: Handler = async (args, ctx) => {
|
|
|
623
410
|
validateRequired(body_of_work_id, 'body_of_work_id');
|
|
624
411
|
validateUUID(body_of_work_id, 'body_of_work_id');
|
|
625
412
|
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (bow.status !== 'active') {
|
|
640
|
-
return {
|
|
641
|
-
result: {
|
|
642
|
-
next_task: null,
|
|
643
|
-
message: `Body of work is ${bow.status}, not active`,
|
|
644
|
-
},
|
|
413
|
+
const apiClient = getApiClient();
|
|
414
|
+
|
|
415
|
+
const response = await apiClient.proxy<{
|
|
416
|
+
next_task: {
|
|
417
|
+
id: string;
|
|
418
|
+
title: string;
|
|
419
|
+
phase: string;
|
|
420
|
+
priority: number;
|
|
421
|
+
} | null;
|
|
422
|
+
body_of_work?: {
|
|
423
|
+
id: string;
|
|
424
|
+
title: string;
|
|
645
425
|
};
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
// Get all tasks with their status and phase
|
|
649
|
-
const { data: taskLinks, error: taskError } = await supabase
|
|
650
|
-
.from('body_of_work_tasks')
|
|
651
|
-
.select(`
|
|
652
|
-
task_id,
|
|
653
|
-
phase,
|
|
654
|
-
order_index,
|
|
655
|
-
tasks (
|
|
656
|
-
id,
|
|
657
|
-
title,
|
|
658
|
-
status,
|
|
659
|
-
priority,
|
|
660
|
-
claimed_by_session_id
|
|
661
|
-
)
|
|
662
|
-
`)
|
|
663
|
-
.eq('body_of_work_id', body_of_work_id)
|
|
664
|
-
.order('order_index');
|
|
665
|
-
|
|
666
|
-
if (taskError) throw new Error(`Failed to fetch tasks: ${taskError.message}`);
|
|
667
|
-
|
|
668
|
-
// Get all dependencies
|
|
669
|
-
const { data: dependencies } = await supabase
|
|
670
|
-
.from('body_of_work_task_dependencies')
|
|
671
|
-
.select('task_id, depends_on_task_id')
|
|
672
|
-
.eq('body_of_work_id', body_of_work_id);
|
|
673
|
-
|
|
674
|
-
const depMap = new Map<string, string[]>();
|
|
675
|
-
for (const dep of dependencies || []) {
|
|
676
|
-
const existing = depMap.get(dep.task_id) || [];
|
|
677
|
-
existing.push(dep.depends_on_task_id);
|
|
678
|
-
depMap.set(dep.task_id, existing);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// Build task status map
|
|
682
|
-
const taskStatusMap = new Map<string, string>();
|
|
683
|
-
for (const link of taskLinks || []) {
|
|
684
|
-
const task = link.tasks as unknown as { id: string; status: string } | null;
|
|
685
|
-
if (task) {
|
|
686
|
-
taskStatusMap.set(task.id, task.status);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Check if all dependencies are completed
|
|
691
|
-
const areDependenciesComplete = (taskId: string): boolean => {
|
|
692
|
-
const deps = depMap.get(taskId) || [];
|
|
693
|
-
return deps.every((depId) => taskStatusMap.get(depId) === 'completed');
|
|
694
|
-
};
|
|
695
|
-
|
|
696
|
-
// Phase order: pre -> core -> post
|
|
697
|
-
const phaseOrder = ['pre', 'core', 'post'];
|
|
698
|
-
|
|
699
|
-
// Check if all tasks in a phase are completed
|
|
700
|
-
const isPhaseComplete = (phase: string): boolean => {
|
|
701
|
-
return (taskLinks || [])
|
|
702
|
-
.filter((link) => link.phase === phase)
|
|
703
|
-
.every((link) => {
|
|
704
|
-
const task = link.tasks as unknown as { status: string } | null;
|
|
705
|
-
return task?.status === 'completed';
|
|
706
|
-
});
|
|
707
|
-
};
|
|
426
|
+
message?: string;
|
|
427
|
+
}>('get_next_body_of_work_task', { body_of_work_id });
|
|
708
428
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
// Check if we can work on this phase
|
|
712
|
-
const prevPhaseIndex = phaseOrder.indexOf(phase) - 1;
|
|
713
|
-
if (prevPhaseIndex >= 0 && !isPhaseComplete(phaseOrder[prevPhaseIndex])) {
|
|
714
|
-
continue; // Previous phase not complete, skip this phase
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const phaseTasks = (taskLinks || [])
|
|
718
|
-
.filter((link) => link.phase === phase)
|
|
719
|
-
.sort((a, b) => a.order_index - b.order_index);
|
|
720
|
-
|
|
721
|
-
for (const link of phaseTasks) {
|
|
722
|
-
const task = link.tasks as unknown as {
|
|
723
|
-
id: string;
|
|
724
|
-
title: string;
|
|
725
|
-
status: string;
|
|
726
|
-
priority: number;
|
|
727
|
-
claimed_by_session_id: string | null;
|
|
728
|
-
} | null;
|
|
729
|
-
|
|
730
|
-
if (!task) continue;
|
|
731
|
-
|
|
732
|
-
// Skip completed, cancelled, or in-progress tasks
|
|
733
|
-
if (task.status !== 'pending') continue;
|
|
734
|
-
|
|
735
|
-
// Skip tasks claimed by another session
|
|
736
|
-
if (task.claimed_by_session_id) continue;
|
|
737
|
-
|
|
738
|
-
// Check dependencies
|
|
739
|
-
if (!areDependenciesComplete(task.id)) continue;
|
|
740
|
-
|
|
741
|
-
return {
|
|
742
|
-
result: {
|
|
743
|
-
next_task: {
|
|
744
|
-
id: task.id,
|
|
745
|
-
title: task.title,
|
|
746
|
-
phase: link.phase,
|
|
747
|
-
priority: task.priority,
|
|
748
|
-
},
|
|
749
|
-
body_of_work: {
|
|
750
|
-
id: body_of_work_id,
|
|
751
|
-
title: bow.title,
|
|
752
|
-
},
|
|
753
|
-
},
|
|
754
|
-
};
|
|
755
|
-
}
|
|
429
|
+
if (!response.ok) {
|
|
430
|
+
throw new Error(`Failed to get next body of work task: ${response.error}`);
|
|
756
431
|
}
|
|
757
432
|
|
|
758
|
-
|
|
759
|
-
return {
|
|
760
|
-
result: {
|
|
761
|
-
next_task: null,
|
|
762
|
-
message: 'No available tasks - all are completed, in progress, or blocked by dependencies',
|
|
763
|
-
},
|
|
764
|
-
};
|
|
433
|
+
return { result: response.data };
|
|
765
434
|
};
|
|
766
435
|
|
|
767
436
|
/**
|