@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.
Files changed (79) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +45 -2
  3. package/dist/api-client.d.ts +276 -8
  4. package/dist/api-client.js +123 -8
  5. package/dist/handlers/blockers.d.ts +11 -0
  6. package/dist/handlers/blockers.js +37 -2
  7. package/dist/handlers/bodies-of-work.d.ts +2 -0
  8. package/dist/handlers/bodies-of-work.js +30 -1
  9. package/dist/handlers/connectors.js +2 -2
  10. package/dist/handlers/decisions.d.ts +11 -0
  11. package/dist/handlers/decisions.js +37 -2
  12. package/dist/handlers/deployment.d.ts +6 -0
  13. package/dist/handlers/deployment.js +33 -5
  14. package/dist/handlers/discovery.js +27 -11
  15. package/dist/handlers/fallback.js +12 -6
  16. package/dist/handlers/file-checkouts.d.ts +1 -0
  17. package/dist/handlers/file-checkouts.js +17 -2
  18. package/dist/handlers/findings.d.ts +5 -0
  19. package/dist/handlers/findings.js +19 -2
  20. package/dist/handlers/git-issues.js +4 -2
  21. package/dist/handlers/ideas.d.ts +5 -0
  22. package/dist/handlers/ideas.js +19 -2
  23. package/dist/handlers/progress.js +2 -2
  24. package/dist/handlers/project.d.ts +1 -0
  25. package/dist/handlers/project.js +35 -2
  26. package/dist/handlers/requests.js +6 -3
  27. package/dist/handlers/roles.js +13 -2
  28. package/dist/handlers/session.d.ts +12 -0
  29. package/dist/handlers/session.js +288 -25
  30. package/dist/handlers/sprints.d.ts +2 -0
  31. package/dist/handlers/sprints.js +30 -1
  32. package/dist/handlers/tasks.d.ts +25 -2
  33. package/dist/handlers/tasks.js +228 -35
  34. package/dist/handlers/tool-docs.js +72 -5
  35. package/dist/templates/agent-guidelines.d.ts +18 -0
  36. package/dist/templates/agent-guidelines.js +207 -0
  37. package/dist/tools.js +478 -125
  38. package/dist/utils.d.ts +5 -2
  39. package/dist/utils.js +90 -51
  40. package/package.json +51 -46
  41. package/scripts/version-bump.ts +203 -0
  42. package/src/api-client.ts +371 -12
  43. package/src/handlers/__test-setup__.ts +5 -0
  44. package/src/handlers/blockers.test.ts +76 -0
  45. package/src/handlers/blockers.ts +56 -2
  46. package/src/handlers/bodies-of-work.ts +59 -1
  47. package/src/handlers/connectors.ts +2 -2
  48. package/src/handlers/decisions.test.ts +71 -2
  49. package/src/handlers/decisions.ts +56 -2
  50. package/src/handlers/deployment.test.ts +81 -0
  51. package/src/handlers/deployment.ts +38 -5
  52. package/src/handlers/discovery.ts +27 -11
  53. package/src/handlers/fallback.test.ts +11 -10
  54. package/src/handlers/fallback.ts +14 -8
  55. package/src/handlers/file-checkouts.test.ts +83 -3
  56. package/src/handlers/file-checkouts.ts +22 -2
  57. package/src/handlers/findings.test.ts +2 -2
  58. package/src/handlers/findings.ts +38 -2
  59. package/src/handlers/git-issues.test.ts +2 -2
  60. package/src/handlers/git-issues.ts +4 -2
  61. package/src/handlers/ideas.test.ts +1 -1
  62. package/src/handlers/ideas.ts +34 -2
  63. package/src/handlers/progress.ts +2 -2
  64. package/src/handlers/project.ts +47 -2
  65. package/src/handlers/requests.test.ts +38 -7
  66. package/src/handlers/requests.ts +6 -3
  67. package/src/handlers/roles.test.ts +1 -1
  68. package/src/handlers/roles.ts +20 -2
  69. package/src/handlers/session.test.ts +303 -4
  70. package/src/handlers/session.ts +348 -35
  71. package/src/handlers/sprints.ts +61 -1
  72. package/src/handlers/tasks.test.ts +0 -73
  73. package/src/handlers/tasks.ts +269 -40
  74. package/src/handlers/tool-docs.ts +77 -5
  75. package/src/handlers/types.test.ts +259 -0
  76. package/src/templates/agent-guidelines.ts +210 -0
  77. package/src/tools.ts +479 -125
  78. package/src/utils.test.ts +7 -5
  79. 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
  // ============================================================================
@@ -2,7 +2,11 @@
2
2
  * Task Handlers (Migrated to API Client)
3
3
  *
4
4
  * Handles task CRUD and management:
5
- * - get_tasks
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
- get_tasks: getTasks,
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
- get_tasks: `# get_tasks
96
- Get tasks for a project.
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
- - status (optional): pending, in_progress, completed, cancelled
101
- - limit (optional): Max tasks (default 50)`,
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 up to 6 separate tool calls (get_findings, get_decisions, get_tasks, get_blockers, etc.) with one call.
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
  };