@vibescope/mcp-server 0.4.4 → 0.4.6
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/CHANGELOG.md +84 -84
- package/README.md +194 -194
- package/dist/api-client/bodies-of-work.d.ts +125 -0
- package/dist/api-client/bodies-of-work.js +78 -0
- package/dist/api-client/chat.d.ts +26 -0
- package/dist/api-client/chat.js +20 -0
- package/dist/api-client/connectors.d.ts +104 -0
- package/dist/api-client/connectors.js +46 -0
- package/dist/api-client/deployment.d.ts +190 -0
- package/dist/api-client/deployment.js +113 -0
- package/dist/api-client/file-checkouts.d.ts +71 -0
- package/dist/api-client/file-checkouts.js +43 -0
- package/dist/api-client/git-issues.d.ts +55 -0
- package/dist/api-client/git-issues.js +34 -0
- package/dist/api-client/index.d.ts +619 -1
- package/dist/api-client/index.js +148 -0
- package/dist/api-client/organizations.d.ts +101 -0
- package/dist/api-client/organizations.js +86 -0
- package/dist/api-client/progress.d.ts +61 -0
- package/dist/api-client/progress.js +34 -0
- package/dist/api-client/project.d.ts +1 -0
- package/dist/api-client/requests.d.ts +28 -0
- package/dist/api-client/requests.js +28 -0
- package/dist/api-client/sprints.d.ts +153 -0
- package/dist/api-client/sprints.js +82 -0
- package/dist/api-client/subtasks.d.ts +37 -0
- package/dist/api-client/subtasks.js +23 -0
- package/dist/api-client.d.ts +23 -0
- package/dist/api-client.js +15 -0
- package/dist/cli-init.js +21 -21
- package/dist/cli.js +26 -26
- package/dist/handlers/blockers.js +4 -0
- package/dist/handlers/chat.d.ts +23 -0
- package/dist/handlers/chat.js +84 -0
- package/dist/handlers/deployment.d.ts +3 -0
- package/dist/handlers/deployment.js +23 -0
- package/dist/handlers/discovery.js +13 -0
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/project.js +4 -2
- package/dist/handlers/session.js +7 -0
- package/dist/handlers/tasks.js +7 -0
- package/dist/handlers/tool-docs.js +1204 -1131
- package/dist/index.js +73 -73
- package/dist/templates/agent-guidelines.d.ts +1 -1
- package/dist/templates/agent-guidelines.js +205 -187
- package/dist/templates/help-content.js +1621 -1621
- package/dist/tools/bodies-of-work.js +6 -6
- package/dist/tools/chat.d.ts +1 -0
- package/dist/tools/chat.js +24 -0
- package/dist/tools/cloud-agents.js +22 -22
- package/dist/tools/deployment.js +13 -0
- package/dist/tools/features.d.ts +13 -0
- package/dist/tools/features.js +151 -0
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.js +4 -1
- package/dist/tools/milestones.js +2 -2
- package/dist/tools/project.js +4 -0
- package/dist/tools/requests.js +1 -1
- package/dist/tools/session.js +11 -11
- package/dist/tools/sprints.js +9 -9
- package/dist/tools/tasks.js +35 -35
- package/dist/tools/worktrees.js +14 -14
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +3602 -0
- package/dist/utils.js +11 -11
- package/docs/TOOLS.md +2663 -2545
- package/package.json +53 -53
- package/scripts/generate-docs.ts +212 -212
- package/scripts/version-bump.ts +203 -203
- package/src/api-client/blockers.ts +86 -86
- package/src/api-client/bodies-of-work.ts +194 -0
- package/src/api-client/chat.ts +50 -0
- package/src/api-client/connectors.ts +152 -0
- package/src/api-client/cost.ts +185 -185
- package/src/api-client/decisions.ts +87 -87
- package/src/api-client/deployment.ts +313 -0
- package/src/api-client/discovery.ts +81 -81
- package/src/api-client/fallback.ts +52 -52
- package/src/api-client/file-checkouts.ts +115 -0
- package/src/api-client/findings.ts +100 -100
- package/src/api-client/git-issues.ts +88 -0
- package/src/api-client/ideas.ts +112 -112
- package/src/api-client/index.ts +592 -426
- package/src/api-client/milestones.ts +83 -83
- package/src/api-client/organizations.ts +185 -0
- package/src/api-client/progress.ts +94 -0
- package/src/api-client/project.ts +180 -179
- package/src/api-client/requests.ts +54 -0
- package/src/api-client/session.ts +220 -220
- package/src/api-client/sprints.ts +227 -0
- package/src/api-client/subtasks.ts +57 -0
- package/src/api-client/tasks.ts +450 -450
- package/src/api-client/types.ts +32 -32
- package/src/api-client/validation.ts +60 -60
- package/src/api-client/worktrees.ts +53 -53
- package/src/api-client.test.ts +847 -850
- package/src/api-client.ts +2707 -2672
- package/src/cli-init.ts +557 -557
- package/src/cli.test.ts +284 -284
- package/src/cli.ts +204 -204
- package/src/handlers/__test-setup__.ts +240 -236
- package/src/handlers/__test-utils__.ts +89 -89
- package/src/handlers/blockers.test.ts +468 -468
- package/src/handlers/blockers.ts +172 -163
- package/src/handlers/bodies-of-work.test.ts +704 -704
- package/src/handlers/bodies-of-work.ts +526 -526
- package/src/handlers/chat.test.ts +185 -0
- package/src/handlers/chat.ts +101 -0
- package/src/handlers/cloud-agents.test.ts +438 -438
- package/src/handlers/cloud-agents.ts +156 -156
- package/src/handlers/connectors.test.ts +834 -834
- package/src/handlers/connectors.ts +229 -229
- package/src/handlers/cost.test.ts +462 -462
- package/src/handlers/cost.ts +285 -285
- package/src/handlers/decisions.test.ts +382 -382
- package/src/handlers/decisions.ts +153 -153
- package/src/handlers/deployment.test.ts +551 -551
- package/src/handlers/deployment.ts +570 -541
- package/src/handlers/discovery.test.ts +206 -206
- package/src/handlers/discovery.ts +427 -414
- package/src/handlers/fallback.test.ts +537 -537
- package/src/handlers/fallback.ts +194 -194
- package/src/handlers/file-checkouts.test.ts +750 -750
- package/src/handlers/file-checkouts.ts +185 -185
- package/src/handlers/findings.test.ts +633 -633
- package/src/handlers/findings.ts +239 -239
- package/src/handlers/git-issues.test.ts +631 -631
- package/src/handlers/git-issues.ts +136 -136
- package/src/handlers/ideas.test.ts +644 -644
- package/src/handlers/ideas.ts +207 -207
- package/src/handlers/index.ts +93 -90
- package/src/handlers/milestones.test.ts +475 -475
- package/src/handlers/milestones.ts +180 -180
- package/src/handlers/organizations.test.ts +826 -826
- package/src/handlers/organizations.ts +315 -315
- package/src/handlers/progress.test.ts +269 -269
- package/src/handlers/progress.ts +77 -77
- package/src/handlers/project.test.ts +546 -546
- package/src/handlers/project.ts +242 -239
- package/src/handlers/requests.test.ts +303 -303
- package/src/handlers/requests.ts +99 -99
- package/src/handlers/roles.test.ts +305 -305
- package/src/handlers/roles.ts +219 -219
- package/src/handlers/session.test.ts +998 -998
- package/src/handlers/session.ts +1105 -1093
- package/src/handlers/sprints.test.ts +732 -732
- package/src/handlers/sprints.ts +537 -537
- package/src/handlers/tasks.test.ts +931 -931
- package/src/handlers/tasks.ts +1133 -1121
- package/src/handlers/tool-categories.test.ts +66 -66
- package/src/handlers/tool-docs.test.ts +511 -511
- package/src/handlers/tool-docs.ts +1571 -1491
- package/src/handlers/types.test.ts +259 -259
- package/src/handlers/types.ts +176 -176
- package/src/handlers/validation.test.ts +582 -582
- package/src/handlers/validation.ts +164 -164
- package/src/handlers/version.ts +63 -63
- package/src/index.test.ts +674 -674
- package/src/index.ts +807 -807
- package/src/setup.test.ts +233 -233
- package/src/setup.ts +404 -404
- package/src/templates/agent-guidelines.ts +233 -215
- package/src/templates/help-content.ts +1751 -1751
- package/src/token-tracking.test.ts +463 -463
- package/src/token-tracking.ts +167 -167
- package/src/tools/blockers.ts +122 -122
- package/src/tools/bodies-of-work.ts +283 -283
- package/src/tools/chat.ts +72 -46
- package/src/tools/cloud-agents.ts +101 -101
- package/src/tools/connectors.ts +191 -191
- package/src/tools/cost.ts +111 -111
- package/src/tools/decisions.ts +111 -111
- package/src/tools/deployment.ts +455 -442
- package/src/tools/discovery.ts +76 -76
- package/src/tools/fallback.ts +111 -111
- package/src/tools/features.ts +154 -0
- package/src/tools/file-checkouts.ts +145 -145
- package/src/tools/findings.ts +101 -101
- package/src/tools/git-issues.ts +130 -130
- package/src/tools/ideas.ts +162 -162
- package/src/tools/index.ts +141 -137
- package/src/tools/milestones.ts +118 -118
- package/src/tools/organizations.ts +224 -224
- package/src/tools/progress.ts +73 -73
- package/src/tools/project.ts +206 -202
- package/src/tools/requests.ts +68 -68
- package/src/tools/roles.ts +112 -112
- package/src/tools/session.ts +181 -181
- package/src/tools/sprints.ts +298 -298
- package/src/tools/tasks.ts +550 -550
- package/src/tools/tools.test.ts +222 -222
- package/src/tools/types.ts +9 -9
- package/src/tools/validation.ts +75 -75
- package/src/tools/version.ts +34 -34
- package/src/tools/worktrees.ts +66 -66
- package/src/tools.test.ts +416 -416
- package/src/utils.test.ts +1014 -1014
- package/src/utils.ts +586 -586
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +249 -249
- package/src/version.ts +109 -109
- package/tsconfig.json +16 -16
- package/vitest.config.ts +14 -14
|
@@ -1,526 +1,526 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bodies of Work Handlers
|
|
3
|
-
*
|
|
4
|
-
* Handles grouping tasks into bodies of work with phases:
|
|
5
|
-
* - create_body_of_work
|
|
6
|
-
* - update_body_of_work
|
|
7
|
-
* - get_body_of_work
|
|
8
|
-
* - get_bodies_of_work
|
|
9
|
-
* - delete_body_of_work
|
|
10
|
-
* - add_task_to_body_of_work
|
|
11
|
-
* - remove_task_from_body_of_work
|
|
12
|
-
* - activate_body_of_work
|
|
13
|
-
* - add_task_dependency
|
|
14
|
-
* - remove_task_dependency
|
|
15
|
-
* - get_task_dependencies
|
|
16
|
-
* - get_next_body_of_work_task
|
|
17
|
-
*
|
|
18
|
-
* MIGRATED: Uses Vibescope API client instead of direct Supabase
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import type { Handler, HandlerRegistry } from './types.js';
|
|
22
|
-
import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
|
|
23
|
-
import { getApiClient } from '../api-client.js';
|
|
24
|
-
|
|
25
|
-
const BODY_OF_WORK_STATUSES = ['draft', 'active', 'completed', 'cancelled', 'archived'] as const;
|
|
26
|
-
const TASK_PHASES = ['pre', 'core', 'post'] as const;
|
|
27
|
-
const DEPLOY_ENVIRONMENTS = ['development', 'staging', 'production'] as const;
|
|
28
|
-
const VERSION_BUMPS = ['patch', 'minor', 'major'] as const;
|
|
29
|
-
const DEPLOY_TRIGGERS = ['all_completed', 'all_completed_validated'] as const;
|
|
30
|
-
|
|
31
|
-
type BodyOfWorkStatus = typeof BODY_OF_WORK_STATUSES[number];
|
|
32
|
-
type TaskPhase = typeof TASK_PHASES[number];
|
|
33
|
-
type DeployEnvironment = typeof DEPLOY_ENVIRONMENTS[number];
|
|
34
|
-
type VersionBump = typeof VERSION_BUMPS[number];
|
|
35
|
-
type DeployTrigger = typeof DEPLOY_TRIGGERS[number];
|
|
36
|
-
|
|
37
|
-
// ============================================================================
|
|
38
|
-
// Argument Schemas
|
|
39
|
-
// ============================================================================
|
|
40
|
-
|
|
41
|
-
const createBodyOfWorkSchema = {
|
|
42
|
-
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
43
|
-
title: { type: 'string' as const, required: true as const },
|
|
44
|
-
description: { type: 'string' as const },
|
|
45
|
-
auto_deploy_on_completion: { type: 'boolean' as const },
|
|
46
|
-
deploy_environment: { type: 'string' as const, validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
|
|
47
|
-
deploy_version_bump: { type: 'string' as const, validate: createEnumValidator(VERSION_BUMPS) },
|
|
48
|
-
deploy_trigger: { type: 'string' as const, validate: createEnumValidator(DEPLOY_TRIGGERS) },
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const updateBodyOfWorkSchema = {
|
|
52
|
-
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
53
|
-
title: { type: 'string' as const },
|
|
54
|
-
description: { type: 'string' as const },
|
|
55
|
-
auto_deploy_on_completion: { type: 'boolean' as const },
|
|
56
|
-
deploy_environment: { type: 'string' as const, validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
|
|
57
|
-
deploy_version_bump: { type: 'string' as const, validate: createEnumValidator(VERSION_BUMPS) },
|
|
58
|
-
deploy_trigger: { type: 'string' as const, validate: createEnumValidator(DEPLOY_TRIGGERS) },
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const getBodyOfWorkSchema = {
|
|
62
|
-
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
63
|
-
summary_only: { type: 'boolean' as const, default: false },
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const getBodiesOfWorkSchema = {
|
|
67
|
-
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
68
|
-
status: { type: 'string' as const, validate: createEnumValidator(BODY_OF_WORK_STATUSES) },
|
|
69
|
-
limit: { type: 'number' as const, default: 50 },
|
|
70
|
-
offset: { type: 'number' as const, default: 0 },
|
|
71
|
-
search_query: { type: 'string' as const },
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const deleteBodyOfWorkSchema = {
|
|
75
|
-
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const addTaskToBodyOfWorkSchema = {
|
|
79
|
-
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
80
|
-
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
81
|
-
phase: { type: 'string' as const, validate: createEnumValidator(TASK_PHASES) },
|
|
82
|
-
order_index: { type: 'number' as const },
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const removeTaskFromBodyOfWorkSchema = {
|
|
86
|
-
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const activateBodyOfWorkSchema = {
|
|
90
|
-
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const addTaskDependencySchema = {
|
|
94
|
-
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
95
|
-
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
96
|
-
depends_on_task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const removeTaskDependencySchema = {
|
|
100
|
-
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
101
|
-
depends_on_task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const getTaskDependenciesSchema = {
|
|
105
|
-
body_of_work_id: { type: 'string' as const, validate: uuidValidator },
|
|
106
|
-
task_id: { type: 'string' as const, validate: uuidValidator },
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const getNextBodyOfWorkTaskSchema = {
|
|
110
|
-
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export const createBodyOfWork: Handler = async (args, ctx) => {
|
|
114
|
-
const {
|
|
115
|
-
project_id,
|
|
116
|
-
title,
|
|
117
|
-
description,
|
|
118
|
-
auto_deploy_on_completion,
|
|
119
|
-
deploy_environment,
|
|
120
|
-
deploy_version_bump,
|
|
121
|
-
deploy_trigger,
|
|
122
|
-
} = parseArgs(args, createBodyOfWorkSchema);
|
|
123
|
-
|
|
124
|
-
const { session } = ctx;
|
|
125
|
-
const apiClient = getApiClient();
|
|
126
|
-
|
|
127
|
-
const response = await apiClient.proxy<{
|
|
128
|
-
success: boolean;
|
|
129
|
-
body_of_work_id: string;
|
|
130
|
-
}>('create_body_of_work', {
|
|
131
|
-
project_id,
|
|
132
|
-
title,
|
|
133
|
-
description,
|
|
134
|
-
auto_deploy_on_completion,
|
|
135
|
-
deploy_environment,
|
|
136
|
-
deploy_version_bump,
|
|
137
|
-
deploy_trigger
|
|
138
|
-
}, {
|
|
139
|
-
session_id: session.currentSessionId,
|
|
140
|
-
persona: session.currentPersona,
|
|
141
|
-
instance_id: session.instanceId
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
if (!response.ok) {
|
|
145
|
-
return { result: { error: response.error || 'Failed to create body of work' }, isError: true };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
result: {
|
|
150
|
-
success: true,
|
|
151
|
-
body_of_work_id: response.data?.body_of_work_id,
|
|
152
|
-
title,
|
|
153
|
-
status: 'draft',
|
|
154
|
-
message: 'Body of work created. Add tasks with add_task_to_body_of_work, then activate with activate_body_of_work.',
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
export const updateBodyOfWork: Handler = async (args, ctx) => {
|
|
160
|
-
const {
|
|
161
|
-
body_of_work_id,
|
|
162
|
-
title,
|
|
163
|
-
description,
|
|
164
|
-
auto_deploy_on_completion,
|
|
165
|
-
deploy_environment,
|
|
166
|
-
deploy_version_bump,
|
|
167
|
-
deploy_trigger,
|
|
168
|
-
} = parseArgs(args, updateBodyOfWorkSchema);
|
|
169
|
-
|
|
170
|
-
// Check if any updates provided
|
|
171
|
-
if (title === undefined && description === undefined && auto_deploy_on_completion === undefined &&
|
|
172
|
-
deploy_environment === undefined && deploy_version_bump === undefined && deploy_trigger === undefined) {
|
|
173
|
-
return { result: { success: true, message: 'No updates provided' } };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const apiClient = getApiClient();
|
|
177
|
-
|
|
178
|
-
const response = await apiClient.proxy<{ success: boolean }>('update_body_of_work', {
|
|
179
|
-
body_of_work_id,
|
|
180
|
-
title,
|
|
181
|
-
description,
|
|
182
|
-
auto_deploy_on_completion,
|
|
183
|
-
deploy_environment,
|
|
184
|
-
deploy_version_bump,
|
|
185
|
-
deploy_trigger
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
if (!response.ok) {
|
|
189
|
-
return { result: { error: response.error || 'Failed to update body of work' }, isError: true };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return { result: { success: true, body_of_work_id } };
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
export const getBodyOfWork: Handler = async (args, ctx) => {
|
|
196
|
-
const { body_of_work_id, summary_only } = parseArgs(args, getBodyOfWorkSchema);
|
|
197
|
-
|
|
198
|
-
const apiClient = getApiClient();
|
|
199
|
-
|
|
200
|
-
// Response type varies based on summary_only
|
|
201
|
-
const response = await apiClient.proxy<{
|
|
202
|
-
body_of_work: {
|
|
203
|
-
id: string;
|
|
204
|
-
title: string;
|
|
205
|
-
description?: string;
|
|
206
|
-
status: string;
|
|
207
|
-
progress_percentage: number;
|
|
208
|
-
total_estimated_minutes?: number;
|
|
209
|
-
completed_estimated_minutes?: number;
|
|
210
|
-
};
|
|
211
|
-
// Full response includes tasks grouped by phase
|
|
212
|
-
tasks?: {
|
|
213
|
-
pre: unknown[];
|
|
214
|
-
core: unknown[];
|
|
215
|
-
post: unknown[];
|
|
216
|
-
};
|
|
217
|
-
// Summary response includes counts and next task
|
|
218
|
-
task_counts?: {
|
|
219
|
-
pre: { total: number; completed: number };
|
|
220
|
-
core: { total: number; completed: number };
|
|
221
|
-
post: { total: number; completed: number };
|
|
222
|
-
total: number;
|
|
223
|
-
completed: number;
|
|
224
|
-
in_progress: number;
|
|
225
|
-
};
|
|
226
|
-
current_task?: { id: string; title: string } | null;
|
|
227
|
-
next_task?: { id: string; title: string; priority: number } | null;
|
|
228
|
-
}>('get_body_of_work', { body_of_work_id, summary_only });
|
|
229
|
-
|
|
230
|
-
if (!response.ok) {
|
|
231
|
-
return { result: { error: response.error || 'Failed to get body of work' }, isError: true };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return { result: response.data };
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
export const getBodiesOfWork: Handler = async (args, ctx) => {
|
|
238
|
-
const { project_id, status, limit, offset, search_query } = parseArgs(args, getBodiesOfWorkSchema);
|
|
239
|
-
|
|
240
|
-
const apiClient = getApiClient();
|
|
241
|
-
|
|
242
|
-
const response = await apiClient.proxy<{
|
|
243
|
-
bodies_of_work: Array<{
|
|
244
|
-
id: string;
|
|
245
|
-
title: string;
|
|
246
|
-
description?: string;
|
|
247
|
-
status: string;
|
|
248
|
-
progress_percentage: number;
|
|
249
|
-
total_estimated_minutes?: number;
|
|
250
|
-
completed_estimated_minutes?: number;
|
|
251
|
-
task_counts: {
|
|
252
|
-
pre: { total: number; completed: number };
|
|
253
|
-
core: { total: number; completed: number };
|
|
254
|
-
post: { total: number; completed: number };
|
|
255
|
-
};
|
|
256
|
-
}>;
|
|
257
|
-
total_count: number;
|
|
258
|
-
has_more: boolean;
|
|
259
|
-
}>('get_bodies_of_work', {
|
|
260
|
-
project_id,
|
|
261
|
-
status,
|
|
262
|
-
limit: Math.min(limit ?? 50, 100),
|
|
263
|
-
offset,
|
|
264
|
-
search_query
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
if (!response.ok) {
|
|
268
|
-
return { result: { error: response.error || 'Failed to fetch bodies of work' }, isError: true };
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return { result: response.data };
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
export const deleteBodyOfWork: Handler = async (args, ctx) => {
|
|
275
|
-
const { body_of_work_id } = parseArgs(args, deleteBodyOfWorkSchema);
|
|
276
|
-
|
|
277
|
-
const apiClient = getApiClient();
|
|
278
|
-
|
|
279
|
-
const response = await apiClient.proxy<{ success: boolean }>('delete_body_of_work', {
|
|
280
|
-
body_of_work_id
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
if (!response.ok) {
|
|
284
|
-
return { result: { error: response.error || 'Failed to delete body of work' }, isError: true };
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return { result: { success: true, message: 'Body of work deleted. Tasks are preserved.' } };
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
export const addTaskToBodyOfWork: Handler = async (args, ctx) => {
|
|
291
|
-
const { body_of_work_id, task_id, phase, order_index } = parseArgs(args, addTaskToBodyOfWorkSchema);
|
|
292
|
-
|
|
293
|
-
const apiClient = getApiClient();
|
|
294
|
-
|
|
295
|
-
const response = await apiClient.proxy<{
|
|
296
|
-
success: boolean;
|
|
297
|
-
body_of_work_id: string;
|
|
298
|
-
task_id: string;
|
|
299
|
-
phase: string;
|
|
300
|
-
order_index: number;
|
|
301
|
-
}>('add_task_to_body_of_work', {
|
|
302
|
-
body_of_work_id,
|
|
303
|
-
task_id,
|
|
304
|
-
phase: phase || 'core',
|
|
305
|
-
order_index
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
if (!response.ok) {
|
|
309
|
-
return { result: { error: response.error || 'Failed to add task to body of work' }, isError: true };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return { result: response.data };
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
export const removeTaskFromBodyOfWork: Handler = async (args, ctx) => {
|
|
316
|
-
const { task_id } = parseArgs(args, removeTaskFromBodyOfWorkSchema);
|
|
317
|
-
|
|
318
|
-
const apiClient = getApiClient();
|
|
319
|
-
|
|
320
|
-
const response = await apiClient.proxy<{
|
|
321
|
-
success: boolean;
|
|
322
|
-
body_of_work_id?: string;
|
|
323
|
-
message?: string;
|
|
324
|
-
}>('remove_task_from_body_of_work', { task_id });
|
|
325
|
-
|
|
326
|
-
if (!response.ok) {
|
|
327
|
-
return { result: { error: response.error || 'Failed to remove task from body of work' }, isError: true };
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return { result: response.data };
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
export const activateBodyOfWork: Handler = async (args, ctx) => {
|
|
334
|
-
const { body_of_work_id } = parseArgs(args, activateBodyOfWorkSchema);
|
|
335
|
-
|
|
336
|
-
const apiClient = getApiClient();
|
|
337
|
-
|
|
338
|
-
const response = await apiClient.proxy<{
|
|
339
|
-
success: boolean;
|
|
340
|
-
body_of_work_id: string;
|
|
341
|
-
title: string;
|
|
342
|
-
status: string;
|
|
343
|
-
message: string;
|
|
344
|
-
}>('activate_body_of_work', { body_of_work_id });
|
|
345
|
-
|
|
346
|
-
if (!response.ok) {
|
|
347
|
-
return { result: { error: response.error || 'Failed to activate body of work' }, isError: true };
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return { result: response.data };
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
export const addTaskDependency: Handler = async (args, ctx) => {
|
|
354
|
-
const { body_of_work_id, task_id, depends_on_task_id } = parseArgs(args, addTaskDependencySchema);
|
|
355
|
-
|
|
356
|
-
if (task_id === depends_on_task_id) {
|
|
357
|
-
return { result: { error: 'A task cannot depend on itself' }, isError: true };
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const apiClient = getApiClient();
|
|
361
|
-
|
|
362
|
-
const response = await apiClient.proxy<{
|
|
363
|
-
success: boolean;
|
|
364
|
-
body_of_work_id: string;
|
|
365
|
-
task_id: string;
|
|
366
|
-
depends_on_task_id: string;
|
|
367
|
-
message: string;
|
|
368
|
-
}>('add_task_dependency', {
|
|
369
|
-
body_of_work_id,
|
|
370
|
-
task_id,
|
|
371
|
-
depends_on_task_id
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
if (!response.ok) {
|
|
375
|
-
return { result: { error: response.error || 'Failed to add task dependency' }, isError: true };
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return { result: response.data };
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
export const removeTaskDependency: Handler = async (args, ctx) => {
|
|
382
|
-
const { task_id, depends_on_task_id } = parseArgs(args, removeTaskDependencySchema);
|
|
383
|
-
|
|
384
|
-
const apiClient = getApiClient();
|
|
385
|
-
|
|
386
|
-
const response = await apiClient.proxy<{
|
|
387
|
-
success: boolean;
|
|
388
|
-
task_id: string;
|
|
389
|
-
depends_on_task_id: string;
|
|
390
|
-
}>('remove_task_dependency', {
|
|
391
|
-
task_id,
|
|
392
|
-
depends_on_task_id
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
if (!response.ok) {
|
|
396
|
-
return { result: { error: response.error || 'Failed to remove task dependency' }, isError: true };
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return { result: response.data };
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
export const getTaskDependencies: Handler = async (args, ctx) => {
|
|
403
|
-
const { body_of_work_id, task_id } = parseArgs(args, getTaskDependenciesSchema);
|
|
404
|
-
|
|
405
|
-
if (!body_of_work_id && !task_id) {
|
|
406
|
-
return { result: { error: 'Either body_of_work_id or task_id is required' }, isError: true };
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const apiClient = getApiClient();
|
|
410
|
-
|
|
411
|
-
const response = await apiClient.proxy<{
|
|
412
|
-
dependencies: Array<{
|
|
413
|
-
id: string;
|
|
414
|
-
task_id: string;
|
|
415
|
-
depends_on_task_id: string;
|
|
416
|
-
created_at: string;
|
|
417
|
-
}>;
|
|
418
|
-
}>('get_task_dependencies', {
|
|
419
|
-
body_of_work_id,
|
|
420
|
-
task_id
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
if (!response.ok) {
|
|
424
|
-
return { result: { error: response.error || 'Failed to fetch task dependencies' }, isError: true };
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return { result: response.data };
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
export const getNextBodyOfWorkTask: Handler = async (args, ctx) => {
|
|
431
|
-
const { body_of_work_id } = parseArgs(args, getNextBodyOfWorkTaskSchema);
|
|
432
|
-
|
|
433
|
-
const apiClient = getApiClient();
|
|
434
|
-
|
|
435
|
-
const response = await apiClient.proxy<{
|
|
436
|
-
next_task: {
|
|
437
|
-
id: string;
|
|
438
|
-
title: string;
|
|
439
|
-
phase: string;
|
|
440
|
-
priority: number;
|
|
441
|
-
} | null;
|
|
442
|
-
body_of_work?: {
|
|
443
|
-
id: string;
|
|
444
|
-
title: string;
|
|
445
|
-
};
|
|
446
|
-
message?: string;
|
|
447
|
-
}>('get_next_body_of_work_task', { body_of_work_id });
|
|
448
|
-
|
|
449
|
-
if (!response.ok) {
|
|
450
|
-
return { result: { error: response.error || 'Failed to get next body of work task' }, isError: true };
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
return { result: response.data };
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
// ============================================================================
|
|
457
|
-
// Archive / Unarchive Handlers
|
|
458
|
-
// ============================================================================
|
|
459
|
-
|
|
460
|
-
const archiveBodyOfWorkSchema = {
|
|
461
|
-
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
const unarchiveBodyOfWorkSchema = {
|
|
465
|
-
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
export const archiveBodyOfWork: Handler = async (args, ctx) => {
|
|
469
|
-
const { body_of_work_id } = parseArgs(args, archiveBodyOfWorkSchema);
|
|
470
|
-
|
|
471
|
-
const apiClient = getApiClient();
|
|
472
|
-
|
|
473
|
-
const response = await apiClient.proxy<{
|
|
474
|
-
success: boolean;
|
|
475
|
-
body_of_work_id: string;
|
|
476
|
-
title: string;
|
|
477
|
-
previous_status: string;
|
|
478
|
-
new_status: string;
|
|
479
|
-
}>('archive_body_of_work', { body_of_work_id });
|
|
480
|
-
|
|
481
|
-
if (!response.ok) {
|
|
482
|
-
return { result: { error: response.error || 'Failed to archive body of work' }, isError: true };
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return { result: response.data };
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
export const unarchiveBodyOfWork: Handler = async (args, ctx) => {
|
|
489
|
-
const { body_of_work_id } = parseArgs(args, unarchiveBodyOfWorkSchema);
|
|
490
|
-
|
|
491
|
-
const apiClient = getApiClient();
|
|
492
|
-
|
|
493
|
-
const response = await apiClient.proxy<{
|
|
494
|
-
success: boolean;
|
|
495
|
-
body_of_work_id: string;
|
|
496
|
-
title: string;
|
|
497
|
-
previous_status: string;
|
|
498
|
-
new_status: string;
|
|
499
|
-
}>('unarchive_body_of_work', { body_of_work_id });
|
|
500
|
-
|
|
501
|
-
if (!response.ok) {
|
|
502
|
-
return { result: { error: response.error || 'Failed to unarchive body of work' }, isError: true };
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return { result: response.data };
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Bodies of Work handlers registry
|
|
510
|
-
*/
|
|
511
|
-
export const bodiesOfWorkHandlers: HandlerRegistry = {
|
|
512
|
-
create_body_of_work: createBodyOfWork,
|
|
513
|
-
update_body_of_work: updateBodyOfWork,
|
|
514
|
-
get_body_of_work: getBodyOfWork,
|
|
515
|
-
get_bodies_of_work: getBodiesOfWork,
|
|
516
|
-
delete_body_of_work: deleteBodyOfWork,
|
|
517
|
-
add_task_to_body_of_work: addTaskToBodyOfWork,
|
|
518
|
-
remove_task_from_body_of_work: removeTaskFromBodyOfWork,
|
|
519
|
-
activate_body_of_work: activateBodyOfWork,
|
|
520
|
-
add_task_dependency: addTaskDependency,
|
|
521
|
-
remove_task_dependency: removeTaskDependency,
|
|
522
|
-
get_task_dependencies: getTaskDependencies,
|
|
523
|
-
get_next_body_of_work_task: getNextBodyOfWorkTask,
|
|
524
|
-
archive_body_of_work: archiveBodyOfWork,
|
|
525
|
-
unarchive_body_of_work: unarchiveBodyOfWork,
|
|
526
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Bodies of Work Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles grouping tasks into bodies of work with phases:
|
|
5
|
+
* - create_body_of_work
|
|
6
|
+
* - update_body_of_work
|
|
7
|
+
* - get_body_of_work
|
|
8
|
+
* - get_bodies_of_work
|
|
9
|
+
* - delete_body_of_work
|
|
10
|
+
* - add_task_to_body_of_work
|
|
11
|
+
* - remove_task_from_body_of_work
|
|
12
|
+
* - activate_body_of_work
|
|
13
|
+
* - add_task_dependency
|
|
14
|
+
* - remove_task_dependency
|
|
15
|
+
* - get_task_dependencies
|
|
16
|
+
* - get_next_body_of_work_task
|
|
17
|
+
*
|
|
18
|
+
* MIGRATED: Uses Vibescope API client instead of direct Supabase
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { Handler, HandlerRegistry } from './types.js';
|
|
22
|
+
import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
|
|
23
|
+
import { getApiClient } from '../api-client.js';
|
|
24
|
+
|
|
25
|
+
const BODY_OF_WORK_STATUSES = ['draft', 'active', 'completed', 'cancelled', 'archived'] as const;
|
|
26
|
+
const TASK_PHASES = ['pre', 'core', 'post'] as const;
|
|
27
|
+
const DEPLOY_ENVIRONMENTS = ['development', 'staging', 'production'] as const;
|
|
28
|
+
const VERSION_BUMPS = ['patch', 'minor', 'major'] as const;
|
|
29
|
+
const DEPLOY_TRIGGERS = ['all_completed', 'all_completed_validated'] as const;
|
|
30
|
+
|
|
31
|
+
type BodyOfWorkStatus = typeof BODY_OF_WORK_STATUSES[number];
|
|
32
|
+
type TaskPhase = typeof TASK_PHASES[number];
|
|
33
|
+
type DeployEnvironment = typeof DEPLOY_ENVIRONMENTS[number];
|
|
34
|
+
type VersionBump = typeof VERSION_BUMPS[number];
|
|
35
|
+
type DeployTrigger = typeof DEPLOY_TRIGGERS[number];
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Argument Schemas
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
const createBodyOfWorkSchema = {
|
|
42
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
43
|
+
title: { type: 'string' as const, required: true as const },
|
|
44
|
+
description: { type: 'string' as const },
|
|
45
|
+
auto_deploy_on_completion: { type: 'boolean' as const },
|
|
46
|
+
deploy_environment: { type: 'string' as const, validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
|
|
47
|
+
deploy_version_bump: { type: 'string' as const, validate: createEnumValidator(VERSION_BUMPS) },
|
|
48
|
+
deploy_trigger: { type: 'string' as const, validate: createEnumValidator(DEPLOY_TRIGGERS) },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const updateBodyOfWorkSchema = {
|
|
52
|
+
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
53
|
+
title: { type: 'string' as const },
|
|
54
|
+
description: { type: 'string' as const },
|
|
55
|
+
auto_deploy_on_completion: { type: 'boolean' as const },
|
|
56
|
+
deploy_environment: { type: 'string' as const, validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
|
|
57
|
+
deploy_version_bump: { type: 'string' as const, validate: createEnumValidator(VERSION_BUMPS) },
|
|
58
|
+
deploy_trigger: { type: 'string' as const, validate: createEnumValidator(DEPLOY_TRIGGERS) },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const getBodyOfWorkSchema = {
|
|
62
|
+
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
63
|
+
summary_only: { type: 'boolean' as const, default: false },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const getBodiesOfWorkSchema = {
|
|
67
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
68
|
+
status: { type: 'string' as const, validate: createEnumValidator(BODY_OF_WORK_STATUSES) },
|
|
69
|
+
limit: { type: 'number' as const, default: 50 },
|
|
70
|
+
offset: { type: 'number' as const, default: 0 },
|
|
71
|
+
search_query: { type: 'string' as const },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const deleteBodyOfWorkSchema = {
|
|
75
|
+
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const addTaskToBodyOfWorkSchema = {
|
|
79
|
+
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
80
|
+
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
81
|
+
phase: { type: 'string' as const, validate: createEnumValidator(TASK_PHASES) },
|
|
82
|
+
order_index: { type: 'number' as const },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const removeTaskFromBodyOfWorkSchema = {
|
|
86
|
+
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const activateBodyOfWorkSchema = {
|
|
90
|
+
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const addTaskDependencySchema = {
|
|
94
|
+
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
95
|
+
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
96
|
+
depends_on_task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const removeTaskDependencySchema = {
|
|
100
|
+
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
101
|
+
depends_on_task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const getTaskDependenciesSchema = {
|
|
105
|
+
body_of_work_id: { type: 'string' as const, validate: uuidValidator },
|
|
106
|
+
task_id: { type: 'string' as const, validate: uuidValidator },
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const getNextBodyOfWorkTaskSchema = {
|
|
110
|
+
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const createBodyOfWork: Handler = async (args, ctx) => {
|
|
114
|
+
const {
|
|
115
|
+
project_id,
|
|
116
|
+
title,
|
|
117
|
+
description,
|
|
118
|
+
auto_deploy_on_completion,
|
|
119
|
+
deploy_environment,
|
|
120
|
+
deploy_version_bump,
|
|
121
|
+
deploy_trigger,
|
|
122
|
+
} = parseArgs(args, createBodyOfWorkSchema);
|
|
123
|
+
|
|
124
|
+
const { session } = ctx;
|
|
125
|
+
const apiClient = getApiClient();
|
|
126
|
+
|
|
127
|
+
const response = await apiClient.proxy<{
|
|
128
|
+
success: boolean;
|
|
129
|
+
body_of_work_id: string;
|
|
130
|
+
}>('create_body_of_work', {
|
|
131
|
+
project_id,
|
|
132
|
+
title,
|
|
133
|
+
description,
|
|
134
|
+
auto_deploy_on_completion,
|
|
135
|
+
deploy_environment,
|
|
136
|
+
deploy_version_bump,
|
|
137
|
+
deploy_trigger
|
|
138
|
+
}, {
|
|
139
|
+
session_id: session.currentSessionId,
|
|
140
|
+
persona: session.currentPersona,
|
|
141
|
+
instance_id: session.instanceId
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
return { result: { error: response.error || 'Failed to create body of work' }, isError: true };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
result: {
|
|
150
|
+
success: true,
|
|
151
|
+
body_of_work_id: response.data?.body_of_work_id,
|
|
152
|
+
title,
|
|
153
|
+
status: 'draft',
|
|
154
|
+
message: 'Body of work created. Add tasks with add_task_to_body_of_work, then activate with activate_body_of_work.',
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const updateBodyOfWork: Handler = async (args, ctx) => {
|
|
160
|
+
const {
|
|
161
|
+
body_of_work_id,
|
|
162
|
+
title,
|
|
163
|
+
description,
|
|
164
|
+
auto_deploy_on_completion,
|
|
165
|
+
deploy_environment,
|
|
166
|
+
deploy_version_bump,
|
|
167
|
+
deploy_trigger,
|
|
168
|
+
} = parseArgs(args, updateBodyOfWorkSchema);
|
|
169
|
+
|
|
170
|
+
// Check if any updates provided
|
|
171
|
+
if (title === undefined && description === undefined && auto_deploy_on_completion === undefined &&
|
|
172
|
+
deploy_environment === undefined && deploy_version_bump === undefined && deploy_trigger === undefined) {
|
|
173
|
+
return { result: { success: true, message: 'No updates provided' } };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const apiClient = getApiClient();
|
|
177
|
+
|
|
178
|
+
const response = await apiClient.proxy<{ success: boolean }>('update_body_of_work', {
|
|
179
|
+
body_of_work_id,
|
|
180
|
+
title,
|
|
181
|
+
description,
|
|
182
|
+
auto_deploy_on_completion,
|
|
183
|
+
deploy_environment,
|
|
184
|
+
deploy_version_bump,
|
|
185
|
+
deploy_trigger
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
return { result: { error: response.error || 'Failed to update body of work' }, isError: true };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { result: { success: true, body_of_work_id } };
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export const getBodyOfWork: Handler = async (args, ctx) => {
|
|
196
|
+
const { body_of_work_id, summary_only } = parseArgs(args, getBodyOfWorkSchema);
|
|
197
|
+
|
|
198
|
+
const apiClient = getApiClient();
|
|
199
|
+
|
|
200
|
+
// Response type varies based on summary_only
|
|
201
|
+
const response = await apiClient.proxy<{
|
|
202
|
+
body_of_work: {
|
|
203
|
+
id: string;
|
|
204
|
+
title: string;
|
|
205
|
+
description?: string;
|
|
206
|
+
status: string;
|
|
207
|
+
progress_percentage: number;
|
|
208
|
+
total_estimated_minutes?: number;
|
|
209
|
+
completed_estimated_minutes?: number;
|
|
210
|
+
};
|
|
211
|
+
// Full response includes tasks grouped by phase
|
|
212
|
+
tasks?: {
|
|
213
|
+
pre: unknown[];
|
|
214
|
+
core: unknown[];
|
|
215
|
+
post: unknown[];
|
|
216
|
+
};
|
|
217
|
+
// Summary response includes counts and next task
|
|
218
|
+
task_counts?: {
|
|
219
|
+
pre: { total: number; completed: number };
|
|
220
|
+
core: { total: number; completed: number };
|
|
221
|
+
post: { total: number; completed: number };
|
|
222
|
+
total: number;
|
|
223
|
+
completed: number;
|
|
224
|
+
in_progress: number;
|
|
225
|
+
};
|
|
226
|
+
current_task?: { id: string; title: string } | null;
|
|
227
|
+
next_task?: { id: string; title: string; priority: number } | null;
|
|
228
|
+
}>('get_body_of_work', { body_of_work_id, summary_only });
|
|
229
|
+
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
return { result: { error: response.error || 'Failed to get body of work' }, isError: true };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return { result: response.data };
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const getBodiesOfWork: Handler = async (args, ctx) => {
|
|
238
|
+
const { project_id, status, limit, offset, search_query } = parseArgs(args, getBodiesOfWorkSchema);
|
|
239
|
+
|
|
240
|
+
const apiClient = getApiClient();
|
|
241
|
+
|
|
242
|
+
const response = await apiClient.proxy<{
|
|
243
|
+
bodies_of_work: Array<{
|
|
244
|
+
id: string;
|
|
245
|
+
title: string;
|
|
246
|
+
description?: string;
|
|
247
|
+
status: string;
|
|
248
|
+
progress_percentage: number;
|
|
249
|
+
total_estimated_minutes?: number;
|
|
250
|
+
completed_estimated_minutes?: number;
|
|
251
|
+
task_counts: {
|
|
252
|
+
pre: { total: number; completed: number };
|
|
253
|
+
core: { total: number; completed: number };
|
|
254
|
+
post: { total: number; completed: number };
|
|
255
|
+
};
|
|
256
|
+
}>;
|
|
257
|
+
total_count: number;
|
|
258
|
+
has_more: boolean;
|
|
259
|
+
}>('get_bodies_of_work', {
|
|
260
|
+
project_id,
|
|
261
|
+
status,
|
|
262
|
+
limit: Math.min(limit ?? 50, 100),
|
|
263
|
+
offset,
|
|
264
|
+
search_query
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
return { result: { error: response.error || 'Failed to fetch bodies of work' }, isError: true };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { result: response.data };
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const deleteBodyOfWork: Handler = async (args, ctx) => {
|
|
275
|
+
const { body_of_work_id } = parseArgs(args, deleteBodyOfWorkSchema);
|
|
276
|
+
|
|
277
|
+
const apiClient = getApiClient();
|
|
278
|
+
|
|
279
|
+
const response = await apiClient.proxy<{ success: boolean }>('delete_body_of_work', {
|
|
280
|
+
body_of_work_id
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (!response.ok) {
|
|
284
|
+
return { result: { error: response.error || 'Failed to delete body of work' }, isError: true };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { result: { success: true, message: 'Body of work deleted. Tasks are preserved.' } };
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export const addTaskToBodyOfWork: Handler = async (args, ctx) => {
|
|
291
|
+
const { body_of_work_id, task_id, phase, order_index } = parseArgs(args, addTaskToBodyOfWorkSchema);
|
|
292
|
+
|
|
293
|
+
const apiClient = getApiClient();
|
|
294
|
+
|
|
295
|
+
const response = await apiClient.proxy<{
|
|
296
|
+
success: boolean;
|
|
297
|
+
body_of_work_id: string;
|
|
298
|
+
task_id: string;
|
|
299
|
+
phase: string;
|
|
300
|
+
order_index: number;
|
|
301
|
+
}>('add_task_to_body_of_work', {
|
|
302
|
+
body_of_work_id,
|
|
303
|
+
task_id,
|
|
304
|
+
phase: phase || 'core',
|
|
305
|
+
order_index
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (!response.ok) {
|
|
309
|
+
return { result: { error: response.error || 'Failed to add task to body of work' }, isError: true };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return { result: response.data };
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export const removeTaskFromBodyOfWork: Handler = async (args, ctx) => {
|
|
316
|
+
const { task_id } = parseArgs(args, removeTaskFromBodyOfWorkSchema);
|
|
317
|
+
|
|
318
|
+
const apiClient = getApiClient();
|
|
319
|
+
|
|
320
|
+
const response = await apiClient.proxy<{
|
|
321
|
+
success: boolean;
|
|
322
|
+
body_of_work_id?: string;
|
|
323
|
+
message?: string;
|
|
324
|
+
}>('remove_task_from_body_of_work', { task_id });
|
|
325
|
+
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
return { result: { error: response.error || 'Failed to remove task from body of work' }, isError: true };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { result: response.data };
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export const activateBodyOfWork: Handler = async (args, ctx) => {
|
|
334
|
+
const { body_of_work_id } = parseArgs(args, activateBodyOfWorkSchema);
|
|
335
|
+
|
|
336
|
+
const apiClient = getApiClient();
|
|
337
|
+
|
|
338
|
+
const response = await apiClient.proxy<{
|
|
339
|
+
success: boolean;
|
|
340
|
+
body_of_work_id: string;
|
|
341
|
+
title: string;
|
|
342
|
+
status: string;
|
|
343
|
+
message: string;
|
|
344
|
+
}>('activate_body_of_work', { body_of_work_id });
|
|
345
|
+
|
|
346
|
+
if (!response.ok) {
|
|
347
|
+
return { result: { error: response.error || 'Failed to activate body of work' }, isError: true };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return { result: response.data };
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
export const addTaskDependency: Handler = async (args, ctx) => {
|
|
354
|
+
const { body_of_work_id, task_id, depends_on_task_id } = parseArgs(args, addTaskDependencySchema);
|
|
355
|
+
|
|
356
|
+
if (task_id === depends_on_task_id) {
|
|
357
|
+
return { result: { error: 'A task cannot depend on itself' }, isError: true };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const apiClient = getApiClient();
|
|
361
|
+
|
|
362
|
+
const response = await apiClient.proxy<{
|
|
363
|
+
success: boolean;
|
|
364
|
+
body_of_work_id: string;
|
|
365
|
+
task_id: string;
|
|
366
|
+
depends_on_task_id: string;
|
|
367
|
+
message: string;
|
|
368
|
+
}>('add_task_dependency', {
|
|
369
|
+
body_of_work_id,
|
|
370
|
+
task_id,
|
|
371
|
+
depends_on_task_id
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (!response.ok) {
|
|
375
|
+
return { result: { error: response.error || 'Failed to add task dependency' }, isError: true };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return { result: response.data };
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export const removeTaskDependency: Handler = async (args, ctx) => {
|
|
382
|
+
const { task_id, depends_on_task_id } = parseArgs(args, removeTaskDependencySchema);
|
|
383
|
+
|
|
384
|
+
const apiClient = getApiClient();
|
|
385
|
+
|
|
386
|
+
const response = await apiClient.proxy<{
|
|
387
|
+
success: boolean;
|
|
388
|
+
task_id: string;
|
|
389
|
+
depends_on_task_id: string;
|
|
390
|
+
}>('remove_task_dependency', {
|
|
391
|
+
task_id,
|
|
392
|
+
depends_on_task_id
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
if (!response.ok) {
|
|
396
|
+
return { result: { error: response.error || 'Failed to remove task dependency' }, isError: true };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return { result: response.data };
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
export const getTaskDependencies: Handler = async (args, ctx) => {
|
|
403
|
+
const { body_of_work_id, task_id } = parseArgs(args, getTaskDependenciesSchema);
|
|
404
|
+
|
|
405
|
+
if (!body_of_work_id && !task_id) {
|
|
406
|
+
return { result: { error: 'Either body_of_work_id or task_id is required' }, isError: true };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const apiClient = getApiClient();
|
|
410
|
+
|
|
411
|
+
const response = await apiClient.proxy<{
|
|
412
|
+
dependencies: Array<{
|
|
413
|
+
id: string;
|
|
414
|
+
task_id: string;
|
|
415
|
+
depends_on_task_id: string;
|
|
416
|
+
created_at: string;
|
|
417
|
+
}>;
|
|
418
|
+
}>('get_task_dependencies', {
|
|
419
|
+
body_of_work_id,
|
|
420
|
+
task_id
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
if (!response.ok) {
|
|
424
|
+
return { result: { error: response.error || 'Failed to fetch task dependencies' }, isError: true };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return { result: response.data };
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
export const getNextBodyOfWorkTask: Handler = async (args, ctx) => {
|
|
431
|
+
const { body_of_work_id } = parseArgs(args, getNextBodyOfWorkTaskSchema);
|
|
432
|
+
|
|
433
|
+
const apiClient = getApiClient();
|
|
434
|
+
|
|
435
|
+
const response = await apiClient.proxy<{
|
|
436
|
+
next_task: {
|
|
437
|
+
id: string;
|
|
438
|
+
title: string;
|
|
439
|
+
phase: string;
|
|
440
|
+
priority: number;
|
|
441
|
+
} | null;
|
|
442
|
+
body_of_work?: {
|
|
443
|
+
id: string;
|
|
444
|
+
title: string;
|
|
445
|
+
};
|
|
446
|
+
message?: string;
|
|
447
|
+
}>('get_next_body_of_work_task', { body_of_work_id });
|
|
448
|
+
|
|
449
|
+
if (!response.ok) {
|
|
450
|
+
return { result: { error: response.error || 'Failed to get next body of work task' }, isError: true };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return { result: response.data };
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// ============================================================================
|
|
457
|
+
// Archive / Unarchive Handlers
|
|
458
|
+
// ============================================================================
|
|
459
|
+
|
|
460
|
+
const archiveBodyOfWorkSchema = {
|
|
461
|
+
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const unarchiveBodyOfWorkSchema = {
|
|
465
|
+
body_of_work_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
export const archiveBodyOfWork: Handler = async (args, ctx) => {
|
|
469
|
+
const { body_of_work_id } = parseArgs(args, archiveBodyOfWorkSchema);
|
|
470
|
+
|
|
471
|
+
const apiClient = getApiClient();
|
|
472
|
+
|
|
473
|
+
const response = await apiClient.proxy<{
|
|
474
|
+
success: boolean;
|
|
475
|
+
body_of_work_id: string;
|
|
476
|
+
title: string;
|
|
477
|
+
previous_status: string;
|
|
478
|
+
new_status: string;
|
|
479
|
+
}>('archive_body_of_work', { body_of_work_id });
|
|
480
|
+
|
|
481
|
+
if (!response.ok) {
|
|
482
|
+
return { result: { error: response.error || 'Failed to archive body of work' }, isError: true };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return { result: response.data };
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
export const unarchiveBodyOfWork: Handler = async (args, ctx) => {
|
|
489
|
+
const { body_of_work_id } = parseArgs(args, unarchiveBodyOfWorkSchema);
|
|
490
|
+
|
|
491
|
+
const apiClient = getApiClient();
|
|
492
|
+
|
|
493
|
+
const response = await apiClient.proxy<{
|
|
494
|
+
success: boolean;
|
|
495
|
+
body_of_work_id: string;
|
|
496
|
+
title: string;
|
|
497
|
+
previous_status: string;
|
|
498
|
+
new_status: string;
|
|
499
|
+
}>('unarchive_body_of_work', { body_of_work_id });
|
|
500
|
+
|
|
501
|
+
if (!response.ok) {
|
|
502
|
+
return { result: { error: response.error || 'Failed to unarchive body of work' }, isError: true };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return { result: response.data };
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Bodies of Work handlers registry
|
|
510
|
+
*/
|
|
511
|
+
export const bodiesOfWorkHandlers: HandlerRegistry = {
|
|
512
|
+
create_body_of_work: createBodyOfWork,
|
|
513
|
+
update_body_of_work: updateBodyOfWork,
|
|
514
|
+
get_body_of_work: getBodyOfWork,
|
|
515
|
+
get_bodies_of_work: getBodiesOfWork,
|
|
516
|
+
delete_body_of_work: deleteBodyOfWork,
|
|
517
|
+
add_task_to_body_of_work: addTaskToBodyOfWork,
|
|
518
|
+
remove_task_from_body_of_work: removeTaskFromBodyOfWork,
|
|
519
|
+
activate_body_of_work: activateBodyOfWork,
|
|
520
|
+
add_task_dependency: addTaskDependency,
|
|
521
|
+
remove_task_dependency: removeTaskDependency,
|
|
522
|
+
get_task_dependencies: getTaskDependencies,
|
|
523
|
+
get_next_body_of_work_task: getNextBodyOfWorkTask,
|
|
524
|
+
archive_body_of_work: archiveBodyOfWork,
|
|
525
|
+
unarchive_body_of_work: unarchiveBodyOfWork,
|
|
526
|
+
};
|