@vibescope/mcp-server 0.2.3 → 0.2.4
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 -0
- package/README.md +45 -2
- package/dist/api-client.d.ts +276 -8
- package/dist/api-client.js +123 -8
- package/dist/handlers/blockers.d.ts +11 -0
- package/dist/handlers/blockers.js +37 -2
- package/dist/handlers/bodies-of-work.d.ts +2 -0
- package/dist/handlers/bodies-of-work.js +30 -1
- package/dist/handlers/connectors.js +2 -2
- package/dist/handlers/decisions.d.ts +11 -0
- package/dist/handlers/decisions.js +37 -2
- package/dist/handlers/deployment.d.ts +6 -0
- package/dist/handlers/deployment.js +33 -5
- package/dist/handlers/discovery.js +27 -11
- package/dist/handlers/fallback.js +12 -6
- package/dist/handlers/file-checkouts.d.ts +1 -0
- package/dist/handlers/file-checkouts.js +17 -2
- package/dist/handlers/findings.d.ts +5 -0
- package/dist/handlers/findings.js +19 -2
- package/dist/handlers/git-issues.js +4 -2
- package/dist/handlers/ideas.d.ts +5 -0
- package/dist/handlers/ideas.js +19 -2
- package/dist/handlers/progress.js +2 -2
- package/dist/handlers/project.d.ts +1 -0
- package/dist/handlers/project.js +35 -2
- package/dist/handlers/requests.js +6 -3
- package/dist/handlers/roles.js +13 -2
- package/dist/handlers/session.d.ts +12 -0
- package/dist/handlers/session.js +288 -25
- package/dist/handlers/sprints.d.ts +2 -0
- package/dist/handlers/sprints.js +30 -1
- package/dist/handlers/tasks.d.ts +25 -2
- package/dist/handlers/tasks.js +228 -35
- package/dist/handlers/tool-docs.js +72 -5
- package/dist/templates/agent-guidelines.d.ts +18 -0
- package/dist/templates/agent-guidelines.js +207 -0
- package/dist/tools.js +478 -125
- package/dist/utils.d.ts +5 -2
- package/dist/utils.js +90 -51
- package/package.json +51 -46
- package/scripts/version-bump.ts +203 -0
- package/src/api-client.ts +371 -12
- package/src/handlers/__test-setup__.ts +5 -0
- package/src/handlers/blockers.test.ts +76 -0
- package/src/handlers/blockers.ts +56 -2
- package/src/handlers/bodies-of-work.ts +59 -1
- package/src/handlers/connectors.ts +2 -2
- package/src/handlers/decisions.test.ts +71 -2
- package/src/handlers/decisions.ts +56 -2
- package/src/handlers/deployment.test.ts +81 -0
- package/src/handlers/deployment.ts +38 -5
- package/src/handlers/discovery.ts +27 -11
- package/src/handlers/fallback.test.ts +11 -10
- package/src/handlers/fallback.ts +14 -8
- package/src/handlers/file-checkouts.test.ts +83 -3
- package/src/handlers/file-checkouts.ts +22 -2
- package/src/handlers/findings.test.ts +2 -2
- package/src/handlers/findings.ts +38 -2
- package/src/handlers/git-issues.test.ts +2 -2
- package/src/handlers/git-issues.ts +4 -2
- package/src/handlers/ideas.test.ts +1 -1
- package/src/handlers/ideas.ts +34 -2
- package/src/handlers/progress.ts +2 -2
- package/src/handlers/project.ts +47 -2
- package/src/handlers/requests.test.ts +38 -7
- package/src/handlers/requests.ts +6 -3
- package/src/handlers/roles.test.ts +1 -1
- package/src/handlers/roles.ts +20 -2
- package/src/handlers/session.test.ts +303 -4
- package/src/handlers/session.ts +348 -35
- package/src/handlers/sprints.ts +61 -1
- package/src/handlers/tasks.test.ts +0 -73
- package/src/handlers/tasks.ts +269 -40
- package/src/handlers/tool-docs.ts +77 -5
- package/src/handlers/types.test.ts +259 -0
- package/src/templates/agent-guidelines.ts +210 -0
- package/src/tools.ts +479 -125
- package/src/utils.test.ts +7 -5
- package/src/utils.ts +95 -51
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import {
|
|
3
|
-
getTasks,
|
|
4
3
|
getNextTask,
|
|
5
4
|
addTask,
|
|
6
5
|
updateTask,
|
|
@@ -18,78 +17,6 @@ import { ValidationError } from '../validators.js';
|
|
|
18
17
|
import { createMockContext } from './__test-utils__.js';
|
|
19
18
|
import { mockApiClient } from './__test-setup__.js';
|
|
20
19
|
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// getTasks Tests
|
|
23
|
-
// ============================================================================
|
|
24
|
-
|
|
25
|
-
describe('getTasks', () => {
|
|
26
|
-
beforeEach(() => vi.clearAllMocks());
|
|
27
|
-
|
|
28
|
-
it('should throw error for missing project_id', async () => {
|
|
29
|
-
const ctx = createMockContext();
|
|
30
|
-
await expect(getTasks({}, ctx)).rejects.toThrow(ValidationError);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should throw error for invalid project_id UUID', async () => {
|
|
34
|
-
const ctx = createMockContext();
|
|
35
|
-
await expect(getTasks({ project_id: 'invalid' }, ctx)).rejects.toThrow(ValidationError);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should throw error for invalid status', async () => {
|
|
39
|
-
const ctx = createMockContext();
|
|
40
|
-
await expect(
|
|
41
|
-
getTasks({ project_id: '123e4567-e89b-12d3-a456-426614174000', status: 'invalid' }, ctx)
|
|
42
|
-
).rejects.toThrow(ValidationError);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should return tasks successfully', async () => {
|
|
46
|
-
mockApiClient.getTasks.mockResolvedValue({
|
|
47
|
-
ok: true,
|
|
48
|
-
data: {
|
|
49
|
-
tasks: [
|
|
50
|
-
{ id: 'task-1', title: 'Test task', status: 'pending', priority: 1 },
|
|
51
|
-
],
|
|
52
|
-
total_count: 1,
|
|
53
|
-
has_more: false,
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const ctx = createMockContext();
|
|
58
|
-
const result = await getTasks(
|
|
59
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
60
|
-
ctx
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
expect(result.result).toMatchObject({
|
|
64
|
-
tasks: expect.any(Array),
|
|
65
|
-
total_count: 1,
|
|
66
|
-
has_more: false,
|
|
67
|
-
});
|
|
68
|
-
expect(mockApiClient.getTasks).toHaveBeenCalledWith(
|
|
69
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
70
|
-
expect.any(Object)
|
|
71
|
-
);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should pass status filter to API', async () => {
|
|
75
|
-
mockApiClient.getTasks.mockResolvedValue({
|
|
76
|
-
ok: true,
|
|
77
|
-
data: { tasks: [], total_count: 0, has_more: false },
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const ctx = createMockContext();
|
|
81
|
-
await getTasks(
|
|
82
|
-
{ project_id: '123e4567-e89b-12d3-a456-426614174000', status: 'pending' },
|
|
83
|
-
ctx
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
expect(mockApiClient.getTasks).toHaveBeenCalledWith(
|
|
87
|
-
'123e4567-e89b-12d3-a456-426614174000',
|
|
88
|
-
expect.objectContaining({ status: 'pending' })
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
20
|
// ============================================================================
|
|
94
21
|
// getNextTask Tests
|
|
95
22
|
// ============================================================================
|
package/src/handlers/tasks.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Task Handlers (Migrated to API Client)
|
|
3
3
|
*
|
|
4
4
|
* Handles task CRUD and management:
|
|
5
|
-
* -
|
|
5
|
+
* - get_task (single task by ID)
|
|
6
|
+
* - search_tasks (text search)
|
|
7
|
+
* - get_tasks_by_priority (priority filter)
|
|
8
|
+
* - get_recent_tasks (by date)
|
|
9
|
+
* - get_task_stats (aggregate counts)
|
|
6
10
|
* - get_next_task
|
|
7
11
|
* - add_task
|
|
8
12
|
* - update_task
|
|
@@ -16,6 +20,7 @@
|
|
|
16
20
|
* - get_subtasks
|
|
17
21
|
*/
|
|
18
22
|
|
|
23
|
+
import os from 'os';
|
|
19
24
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
20
25
|
import {
|
|
21
26
|
parseArgs,
|
|
@@ -29,6 +34,9 @@ import {
|
|
|
29
34
|
} from '../validators.js';
|
|
30
35
|
import { getApiClient } from '../api-client.js';
|
|
31
36
|
|
|
37
|
+
// Auto-detect machine hostname for worktree tracking
|
|
38
|
+
const MACHINE_HOSTNAME = os.hostname();
|
|
39
|
+
|
|
32
40
|
// Valid task types
|
|
33
41
|
const VALID_TASK_TYPES = [
|
|
34
42
|
'frontend', 'backend', 'database', 'feature', 'bugfix',
|
|
@@ -39,16 +47,6 @@ const VALID_TASK_TYPES = [
|
|
|
39
47
|
// Argument Schemas
|
|
40
48
|
// ============================================================================
|
|
41
49
|
|
|
42
|
-
const getTasksSchema = {
|
|
43
|
-
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
44
|
-
status: { type: 'string' as const, validate: taskStatusValidator },
|
|
45
|
-
limit: { type: 'number' as const, default: 50 },
|
|
46
|
-
offset: { type: 'number' as const, default: 0 },
|
|
47
|
-
search_query: { type: 'string' as const },
|
|
48
|
-
include_subtasks: { type: 'boolean' as const, default: false },
|
|
49
|
-
include_metadata: { type: 'boolean' as const, default: false },
|
|
50
|
-
};
|
|
51
|
-
|
|
52
50
|
const getNextTaskSchema = {
|
|
53
51
|
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
54
52
|
};
|
|
@@ -117,6 +115,47 @@ const addSubtaskSchema = {
|
|
|
117
115
|
const getSubtasksSchema = {
|
|
118
116
|
parent_task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
119
117
|
status: { type: 'string' as const, validate: taskStatusValidator },
|
|
118
|
+
limit: { type: 'number' as const, default: 20 },
|
|
119
|
+
offset: { type: 'number' as const, default: 0 },
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// New Targeted Task Query Schemas
|
|
124
|
+
// ============================================================================
|
|
125
|
+
|
|
126
|
+
const getTaskSchema = {
|
|
127
|
+
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
128
|
+
include_subtasks: { type: 'boolean' as const, default: false },
|
|
129
|
+
include_milestones: { type: 'boolean' as const, default: false },
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const searchTasksSchema = {
|
|
133
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
134
|
+
query: { type: 'string' as const, required: true as const },
|
|
135
|
+
status: { type: 'array' as const },
|
|
136
|
+
limit: { type: 'number' as const, default: 10 },
|
|
137
|
+
offset: { type: 'number' as const, default: 0 },
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const getTasksByPrioritySchema = {
|
|
141
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
142
|
+
priority: { type: 'number' as const, validate: priorityValidator },
|
|
143
|
+
priority_max: { type: 'number' as const, validate: priorityValidator },
|
|
144
|
+
status: { type: 'string' as const, validate: taskStatusValidator },
|
|
145
|
+
limit: { type: 'number' as const, default: 10 },
|
|
146
|
+
offset: { type: 'number' as const, default: 0 },
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const getRecentTasksSchema = {
|
|
150
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
151
|
+
order: { type: 'string' as const, validate: createEnumValidator(['newest', 'oldest']) },
|
|
152
|
+
status: { type: 'string' as const, validate: taskStatusValidator },
|
|
153
|
+
limit: { type: 'number' as const, default: 10 },
|
|
154
|
+
offset: { type: 'number' as const, default: 0 },
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const getTaskStatsSchema = {
|
|
158
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
120
159
|
};
|
|
121
160
|
|
|
122
161
|
// ============================================================================
|
|
@@ -216,32 +255,6 @@ export function getValidationApprovedGitInstructions(
|
|
|
216
255
|
// Task Handlers - Using API Client
|
|
217
256
|
// ============================================================================
|
|
218
257
|
|
|
219
|
-
export const getTasks: Handler = async (args, ctx) => {
|
|
220
|
-
const { project_id, status, limit, offset, search_query, include_subtasks, include_metadata } = parseArgs(args, getTasksSchema);
|
|
221
|
-
|
|
222
|
-
const api = getApiClient();
|
|
223
|
-
const response = await api.getTasks(project_id, {
|
|
224
|
-
status,
|
|
225
|
-
limit: Math.min(limit ?? 50, 200),
|
|
226
|
-
offset,
|
|
227
|
-
include_subtasks,
|
|
228
|
-
search_query,
|
|
229
|
-
include_metadata,
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
if (!response.ok) {
|
|
233
|
-
return { result: { error: response.error || 'Failed to fetch tasks' }, isError: true };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return {
|
|
237
|
-
result: {
|
|
238
|
-
tasks: response.data?.tasks || [],
|
|
239
|
-
total_count: response.data?.total_count || 0,
|
|
240
|
-
has_more: response.data?.has_more || false,
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
};
|
|
244
|
-
|
|
245
258
|
export const getNextTask: Handler = async (args, ctx) => {
|
|
246
259
|
const { project_id } = parseArgs(args, getNextTaskSchema);
|
|
247
260
|
|
|
@@ -400,6 +413,17 @@ export const updateTask: Handler = async (args, ctx) => {
|
|
|
400
413
|
result.next_step = data.next_step;
|
|
401
414
|
}
|
|
402
415
|
|
|
416
|
+
// Add test reminder when starting work on a task
|
|
417
|
+
if (status === 'in_progress') {
|
|
418
|
+
result.test_reminder = {
|
|
419
|
+
message: 'Remember to write tests for this task before marking it complete.',
|
|
420
|
+
minimum_expectation: 'Basic tests that validate the task requirements are met',
|
|
421
|
+
ideal: 'Tests that also cover edge cases and error handling',
|
|
422
|
+
test_patterns: ['*.test.ts', '*.spec.ts', '*.test.js', '*.spec.js', '__tests__/*'],
|
|
423
|
+
note: 'Validators will check for test file changes during review. Documentation-only or config changes may not require tests.',
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
403
427
|
return { result };
|
|
404
428
|
};
|
|
405
429
|
|
|
@@ -640,12 +664,199 @@ export const getSubtasks: Handler = async (args, ctx) => {
|
|
|
640
664
|
};
|
|
641
665
|
};
|
|
642
666
|
|
|
667
|
+
// ============================================================================
|
|
668
|
+
// New Targeted Task Query Handlers
|
|
669
|
+
// ============================================================================
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Get a single task by ID with optional subtasks and milestones
|
|
673
|
+
*/
|
|
674
|
+
export const getTask: Handler = async (args, ctx) => {
|
|
675
|
+
const { task_id, include_subtasks, include_milestones } = parseArgs(args, getTaskSchema);
|
|
676
|
+
|
|
677
|
+
const api = getApiClient();
|
|
678
|
+
const response = await api.getTaskById(task_id, {
|
|
679
|
+
include_subtasks,
|
|
680
|
+
include_milestones,
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
if (!response.ok) {
|
|
684
|
+
return { result: { error: response.error || 'Failed to fetch task' }, isError: true };
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const result: Record<string, unknown> = {
|
|
688
|
+
task: response.data?.task,
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
if (include_subtasks && response.data?.subtasks) {
|
|
692
|
+
result.subtasks = response.data.subtasks;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (include_milestones && response.data?.milestones) {
|
|
696
|
+
result.milestones = response.data.milestones;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return { result };
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Search tasks by text query with pagination
|
|
704
|
+
*/
|
|
705
|
+
export const searchTasks: Handler = async (args, ctx) => {
|
|
706
|
+
const { project_id, query, status, limit, offset } = parseArgs(args, searchTasksSchema);
|
|
707
|
+
|
|
708
|
+
// Validate query length
|
|
709
|
+
if (query.length < 2) {
|
|
710
|
+
return {
|
|
711
|
+
result: {
|
|
712
|
+
error: 'query_too_short',
|
|
713
|
+
message: 'Search query must be at least 2 characters',
|
|
714
|
+
},
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Cap limit at 20, safe offset
|
|
719
|
+
const cappedLimit = Math.min(limit ?? 10, 20);
|
|
720
|
+
const safeOffset = Math.max(0, offset ?? 0);
|
|
721
|
+
|
|
722
|
+
const api = getApiClient();
|
|
723
|
+
const response = await api.searchTasks(project_id, {
|
|
724
|
+
query,
|
|
725
|
+
status: status as string[] | undefined,
|
|
726
|
+
limit: cappedLimit,
|
|
727
|
+
offset: safeOffset,
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
if (!response.ok) {
|
|
731
|
+
return { result: { error: response.error || 'Failed to search tasks' }, isError: true };
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const tasks = response.data?.tasks || [];
|
|
735
|
+
const totalMatches = response.data?.total_matches || 0;
|
|
736
|
+
|
|
737
|
+
return {
|
|
738
|
+
result: {
|
|
739
|
+
tasks,
|
|
740
|
+
total_matches: totalMatches,
|
|
741
|
+
has_more: safeOffset + tasks.length < totalMatches,
|
|
742
|
+
offset: safeOffset,
|
|
743
|
+
limit: cappedLimit,
|
|
744
|
+
},
|
|
745
|
+
};
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Get tasks filtered by priority with pagination
|
|
750
|
+
*/
|
|
751
|
+
export const getTasksByPriority: Handler = async (args, ctx) => {
|
|
752
|
+
const { project_id, priority, priority_max, status, limit, offset } = parseArgs(args, getTasksByPrioritySchema);
|
|
753
|
+
|
|
754
|
+
// Cap limit at 20, safe offset
|
|
755
|
+
const cappedLimit = Math.min(limit ?? 10, 20);
|
|
756
|
+
const safeOffset = Math.max(0, offset ?? 0);
|
|
757
|
+
|
|
758
|
+
const api = getApiClient();
|
|
759
|
+
const response = await api.getTasksByPriority(project_id, {
|
|
760
|
+
priority,
|
|
761
|
+
priority_max,
|
|
762
|
+
status,
|
|
763
|
+
limit: cappedLimit,
|
|
764
|
+
offset: safeOffset,
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
if (!response.ok) {
|
|
768
|
+
return { result: { error: response.error || 'Failed to fetch tasks by priority' }, isError: true };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const tasks = response.data?.tasks || [];
|
|
772
|
+
const totalCount = response.data?.total_count || 0;
|
|
773
|
+
|
|
774
|
+
return {
|
|
775
|
+
result: {
|
|
776
|
+
tasks,
|
|
777
|
+
total_count: totalCount,
|
|
778
|
+
has_more: safeOffset + tasks.length < totalCount,
|
|
779
|
+
offset: safeOffset,
|
|
780
|
+
limit: cappedLimit,
|
|
781
|
+
},
|
|
782
|
+
};
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Get recent tasks (newest or oldest) with pagination
|
|
787
|
+
*/
|
|
788
|
+
export const getRecentTasks: Handler = async (args, ctx) => {
|
|
789
|
+
const { project_id, order, status, limit, offset } = parseArgs(args, getRecentTasksSchema);
|
|
790
|
+
|
|
791
|
+
// Cap limit at 20, safe offset
|
|
792
|
+
const cappedLimit = Math.min(limit ?? 10, 20);
|
|
793
|
+
const safeOffset = Math.max(0, offset ?? 0);
|
|
794
|
+
|
|
795
|
+
const api = getApiClient();
|
|
796
|
+
const response = await api.getRecentTasks(project_id, {
|
|
797
|
+
order: order as 'newest' | 'oldest' | undefined,
|
|
798
|
+
status,
|
|
799
|
+
limit: cappedLimit,
|
|
800
|
+
offset: safeOffset,
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
if (!response.ok) {
|
|
804
|
+
return { result: { error: response.error || 'Failed to fetch recent tasks' }, isError: true };
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const tasks = response.data?.tasks || [];
|
|
808
|
+
const totalCount = response.data?.total_count || 0;
|
|
809
|
+
|
|
810
|
+
return {
|
|
811
|
+
result: {
|
|
812
|
+
tasks,
|
|
813
|
+
total_count: totalCount,
|
|
814
|
+
has_more: safeOffset + tasks.length < totalCount,
|
|
815
|
+
offset: safeOffset,
|
|
816
|
+
limit: cappedLimit,
|
|
817
|
+
},
|
|
818
|
+
};
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Get task statistics for a project (aggregate counts only, minimal tokens)
|
|
823
|
+
*/
|
|
824
|
+
export const getTaskStats: Handler = async (args, ctx) => {
|
|
825
|
+
const { project_id } = parseArgs(args, getTaskStatsSchema);
|
|
826
|
+
|
|
827
|
+
const api = getApiClient();
|
|
828
|
+
const response = await api.getTaskStats(project_id);
|
|
829
|
+
|
|
830
|
+
if (!response.ok) {
|
|
831
|
+
return { result: { error: response.error || 'Failed to fetch task stats' }, isError: true };
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return {
|
|
835
|
+
result: {
|
|
836
|
+
total: response.data?.total || 0,
|
|
837
|
+
by_status: response.data?.by_status || {
|
|
838
|
+
backlog: 0,
|
|
839
|
+
pending: 0,
|
|
840
|
+
in_progress: 0,
|
|
841
|
+
completed: 0,
|
|
842
|
+
cancelled: 0,
|
|
843
|
+
},
|
|
844
|
+
by_priority: response.data?.by_priority || { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 },
|
|
845
|
+
awaiting_validation: response.data?.awaiting_validation || 0,
|
|
846
|
+
oldest_pending_days: response.data?.oldest_pending_days ?? null,
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
};
|
|
850
|
+
|
|
643
851
|
// ============================================================================
|
|
644
852
|
// Worktree Cleanup Handlers
|
|
645
853
|
// ============================================================================
|
|
646
854
|
|
|
647
855
|
const getStaleWorktreesSchema = {
|
|
648
856
|
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
857
|
+
hostname: { type: 'string' as const }, // Machine hostname to filter worktrees
|
|
858
|
+
limit: { type: 'number' as const, default: 20 },
|
|
859
|
+
offset: { type: 'number' as const, default: 0 },
|
|
649
860
|
};
|
|
650
861
|
|
|
651
862
|
const clearWorktreePathSchema = {
|
|
@@ -653,10 +864,16 @@ const clearWorktreePathSchema = {
|
|
|
653
864
|
};
|
|
654
865
|
|
|
655
866
|
export const getStaleWorktrees: Handler = async (args, ctx) => {
|
|
656
|
-
const { project_id } = parseArgs(args, getStaleWorktreesSchema);
|
|
867
|
+
const { project_id, hostname: providedHostname, limit, offset } = parseArgs(args, getStaleWorktreesSchema);
|
|
868
|
+
|
|
869
|
+
// Use auto-detected hostname if not provided - filters to only worktrees on THIS machine
|
|
870
|
+
const hostname = providedHostname || MACHINE_HOSTNAME;
|
|
871
|
+
|
|
872
|
+
// Cap limit to prevent context bloating
|
|
873
|
+
const cappedLimit = Math.min(limit ?? 20, 50);
|
|
657
874
|
|
|
658
875
|
const api = getApiClient();
|
|
659
|
-
const response = await api.getStaleWorktrees(project_id);
|
|
876
|
+
const response = await api.getStaleWorktrees(project_id, { hostname, limit: cappedLimit, offset });
|
|
660
877
|
|
|
661
878
|
if (!response.ok) {
|
|
662
879
|
return { result: { error: response.error || 'Failed to get stale worktrees' }, isError: true };
|
|
@@ -667,9 +884,15 @@ export const getStaleWorktrees: Handler = async (args, ctx) => {
|
|
|
667
884
|
result: {
|
|
668
885
|
project_id: data?.project_id,
|
|
669
886
|
project_name: data?.project_name,
|
|
887
|
+
hostname_filter: data?.hostname_filter,
|
|
670
888
|
stale_worktrees: data?.stale_worktrees || [],
|
|
671
889
|
count: data?.count || 0,
|
|
890
|
+
local_count: data?.local_count || 0,
|
|
891
|
+
remote_count: data?.remote_count || 0,
|
|
892
|
+
total_count: data?.total_count || 0,
|
|
893
|
+
has_more: data?.has_more || false,
|
|
672
894
|
cleanup_instructions: data?.cleanup_instructions,
|
|
895
|
+
remote_worktree_note: data?.remote_worktree_note,
|
|
673
896
|
},
|
|
674
897
|
};
|
|
675
898
|
};
|
|
@@ -697,7 +920,13 @@ export const clearWorktreePath: Handler = async (args, ctx) => {
|
|
|
697
920
|
* Task handlers registry
|
|
698
921
|
*/
|
|
699
922
|
export const taskHandlers: HandlerRegistry = {
|
|
700
|
-
|
|
923
|
+
// Targeted task query endpoints (token-efficient)
|
|
924
|
+
get_task: getTask,
|
|
925
|
+
search_tasks: searchTasks,
|
|
926
|
+
get_tasks_by_priority: getTasksByPriority,
|
|
927
|
+
get_recent_tasks: getRecentTasks,
|
|
928
|
+
get_task_stats: getTaskStats,
|
|
929
|
+
// Core task operations
|
|
701
930
|
get_next_task: getNextTask,
|
|
702
931
|
add_task: addTask,
|
|
703
932
|
update_task: updateTask,
|
|
@@ -92,13 +92,73 @@ Sync README content to the dashboard.
|
|
|
92
92
|
- project_id (required): Project UUID
|
|
93
93
|
- readme_content (required): Markdown content`,
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
Get
|
|
95
|
+
get_task: `# get_task
|
|
96
|
+
Get a single task by ID with optional subtasks and milestones.
|
|
97
|
+
|
|
98
|
+
**Parameters:**
|
|
99
|
+
- task_id (required): Task UUID
|
|
100
|
+
- include_subtasks (optional): Include subtasks array (default: false)
|
|
101
|
+
- include_milestones (optional): Include milestones array (default: false)`,
|
|
102
|
+
|
|
103
|
+
search_tasks: `# search_tasks
|
|
104
|
+
Search tasks by text query. Supports pagination.
|
|
105
|
+
|
|
106
|
+
**Parameters:**
|
|
107
|
+
- project_id (required): Project UUID
|
|
108
|
+
- query (required): Search query (min 2 chars)
|
|
109
|
+
- status (optional): Array of statuses to filter
|
|
110
|
+
- limit (optional): Max results per page (1-20, default: 10)
|
|
111
|
+
- offset (optional): Number of results to skip (default: 0)
|
|
112
|
+
|
|
113
|
+
**Returns:**
|
|
114
|
+
- tasks: Array of matching tasks
|
|
115
|
+
- total_matches: Total number of matching tasks
|
|
116
|
+
- has_more: Whether more results exist beyond current page
|
|
117
|
+
- offset: Current offset
|
|
118
|
+
- limit: Current limit`,
|
|
119
|
+
|
|
120
|
+
get_tasks_by_priority: `# get_tasks_by_priority
|
|
121
|
+
Get tasks filtered by priority. Supports pagination.
|
|
122
|
+
|
|
123
|
+
**Parameters:**
|
|
124
|
+
- project_id (required): Project UUID
|
|
125
|
+
- priority (optional): Exact priority (1-5)
|
|
126
|
+
- priority_max (optional): Max priority for range query
|
|
127
|
+
- status (optional): Filter by status (default: pending)
|
|
128
|
+
- limit (optional): Max results per page (1-20, default: 10)
|
|
129
|
+
- offset (optional): Number of results to skip (default: 0)
|
|
130
|
+
|
|
131
|
+
**Returns:**
|
|
132
|
+
- tasks: Array of matching tasks
|
|
133
|
+
- total_count: Total number of matching tasks
|
|
134
|
+
- has_more: Whether more results exist beyond current page
|
|
135
|
+
- offset: Current offset
|
|
136
|
+
- limit: Current limit`,
|
|
137
|
+
|
|
138
|
+
get_recent_tasks: `# get_recent_tasks
|
|
139
|
+
Get tasks ordered by creation date. Supports pagination.
|
|
140
|
+
|
|
141
|
+
**Parameters:**
|
|
142
|
+
- project_id (required): Project UUID
|
|
143
|
+
- order (optional): 'newest' or 'oldest' (default: newest)
|
|
144
|
+
- status (optional): Filter by status
|
|
145
|
+
- limit (optional): Max results per page (1-20, default: 10)
|
|
146
|
+
- offset (optional): Number of results to skip (default: 0)
|
|
147
|
+
|
|
148
|
+
**Returns:**
|
|
149
|
+
- tasks: Array of matching tasks
|
|
150
|
+
- total_count: Total number of matching tasks
|
|
151
|
+
- has_more: Whether more results exist beyond current page
|
|
152
|
+
- offset: Current offset
|
|
153
|
+
- limit: Current limit`,
|
|
154
|
+
|
|
155
|
+
get_task_stats: `# get_task_stats
|
|
156
|
+
Get aggregate task statistics. Most token-efficient way to understand project state.
|
|
97
157
|
|
|
98
158
|
**Parameters:**
|
|
99
159
|
- project_id (required): Project UUID
|
|
100
|
-
|
|
101
|
-
|
|
160
|
+
|
|
161
|
+
**Returns:** Counts by status and priority, no task data`,
|
|
102
162
|
|
|
103
163
|
get_next_task: `# get_next_task
|
|
104
164
|
Get highest priority pending task not claimed by another agent.
|
|
@@ -145,6 +205,18 @@ Delete a task.
|
|
|
145
205
|
**Parameters:**
|
|
146
206
|
- task_id (required): Task UUID`,
|
|
147
207
|
|
|
208
|
+
cancel_task: `# cancel_task
|
|
209
|
+
Cancel a task with an optional reason. Use this when a task should be marked as cancelled rather than deleted (e.g., PR was closed, work was superseded). The task remains visible with a cancelled status for historical tracking.
|
|
210
|
+
|
|
211
|
+
**Parameters:**
|
|
212
|
+
- task_id (required): Task UUID
|
|
213
|
+
- cancelled_reason (optional): One of: pr_closed, superseded, user_cancelled, validation_failed, obsolete, blocked
|
|
214
|
+
- cancellation_note (optional): Additional context about the cancellation
|
|
215
|
+
|
|
216
|
+
**Returns:** task_id, cancelled_reason, message, sprint info (if auto-completed)
|
|
217
|
+
|
|
218
|
+
**Example:** cancel_task(task_id: "abc", cancelled_reason: "pr_closed", cancellation_note: "PR was closed without merging")`,
|
|
219
|
+
|
|
148
220
|
batch_update_tasks: `# batch_update_tasks
|
|
149
221
|
Update multiple tasks at once.
|
|
150
222
|
|
|
@@ -1018,7 +1090,7 @@ Query aggregated project knowledge in a single call. Reduces token usage by comb
|
|
|
1018
1090
|
- stats: counts for each category, findings by severity
|
|
1019
1091
|
- Category data based on selection
|
|
1020
1092
|
|
|
1021
|
-
**Token savings:** Replaces
|
|
1093
|
+
**Token savings:** Replaces multiple tool calls (get_findings, get_decisions, get_blockers, etc.) with one call.
|
|
1022
1094
|
|
|
1023
1095
|
**Example:** query_knowledge_base(project_id, categories: ["findings", "decisions"], limit: 10)`,
|
|
1024
1096
|
};
|