@vibescope/mcp-server 0.3.0 → 0.3.3
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/dist/api-client/blockers.d.ts +46 -0
- package/dist/api-client/blockers.js +43 -0
- package/dist/api-client/cost.d.ts +112 -0
- package/dist/api-client/cost.js +76 -0
- package/dist/api-client/decisions.d.ts +55 -0
- package/dist/api-client/decisions.js +32 -0
- package/dist/api-client/discovery.d.ts +62 -0
- package/dist/api-client/discovery.js +21 -0
- package/dist/api-client/ideas.d.ts +75 -0
- package/dist/api-client/ideas.js +36 -0
- package/dist/api-client/index.d.ts +749 -0
- package/dist/api-client/index.js +291 -0
- package/dist/api-client/project.d.ts +132 -0
- package/dist/api-client/project.js +45 -0
- package/dist/api-client/session.d.ts +163 -0
- package/dist/api-client/session.js +52 -0
- package/dist/api-client/tasks.d.ts +328 -0
- package/dist/api-client/tasks.js +132 -0
- package/dist/api-client/types.d.ts +25 -0
- package/dist/api-client/types.js +4 -0
- package/dist/api-client/worktrees.d.ts +33 -0
- package/dist/api-client/worktrees.js +26 -0
- package/dist/api-client.d.ts +9 -0
- package/dist/api-client.js +104 -25
- package/dist/cli-init.d.ts +17 -0
- package/dist/cli-init.js +445 -0
- package/dist/cli.js +0 -0
- package/dist/handlers/cloud-agents.d.ts +21 -0
- package/dist/handlers/cloud-agents.js +91 -0
- package/dist/handlers/discovery.js +7 -0
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/session.js +3 -1
- package/dist/handlers/tasks.js +10 -12
- package/dist/handlers/types.d.ts +2 -1
- package/dist/handlers/validation.js +5 -1
- package/dist/index.js +8 -3
- package/dist/token-tracking.js +2 -2
- package/dist/tools/blockers.d.ts +13 -0
- package/dist/tools/blockers.js +119 -0
- package/dist/tools/bodies-of-work.d.ts +19 -0
- package/dist/tools/bodies-of-work.js +280 -0
- package/dist/tools/cloud-agents.d.ts +9 -0
- package/dist/tools/cloud-agents.js +67 -0
- package/dist/tools/connectors.d.ts +14 -0
- package/dist/tools/connectors.js +188 -0
- package/dist/tools/cost.d.ts +11 -0
- package/dist/tools/cost.js +108 -0
- package/dist/tools/decisions.d.ts +12 -0
- package/dist/tools/decisions.js +108 -0
- package/dist/tools/deployment.d.ts +24 -0
- package/dist/tools/deployment.js +439 -0
- package/dist/tools/discovery.d.ts +10 -0
- package/dist/tools/discovery.js +73 -0
- package/dist/tools/fallback.d.ts +11 -0
- package/dist/tools/fallback.js +108 -0
- package/dist/tools/file-checkouts.d.ts +13 -0
- package/dist/tools/file-checkouts.js +141 -0
- package/dist/tools/findings.d.ts +13 -0
- package/dist/tools/findings.js +98 -0
- package/dist/tools/git-issues.d.ts +11 -0
- package/dist/tools/git-issues.js +127 -0
- package/dist/tools/ideas.d.ts +13 -0
- package/dist/tools/ideas.js +159 -0
- package/dist/tools/index.d.ts +71 -0
- package/dist/tools/index.js +98 -0
- package/dist/tools/milestones.d.ts +12 -0
- package/dist/tools/milestones.js +115 -0
- package/dist/tools/organizations.d.ts +17 -0
- package/dist/tools/organizations.js +221 -0
- package/dist/tools/progress.d.ts +9 -0
- package/dist/tools/progress.js +70 -0
- package/dist/tools/project.d.ts +13 -0
- package/dist/tools/project.js +199 -0
- package/dist/tools/requests.d.ts +10 -0
- package/dist/tools/requests.js +65 -0
- package/dist/tools/roles.d.ts +11 -0
- package/dist/tools/roles.js +109 -0
- package/dist/tools/session.d.ts +15 -0
- package/dist/tools/session.js +178 -0
- package/dist/tools/sprints.d.ts +18 -0
- package/dist/tools/sprints.js +295 -0
- package/dist/tools/tasks.d.ts +27 -0
- package/dist/tools/tasks.js +539 -0
- package/dist/tools/types.d.ts +7 -0
- package/dist/tools/types.js +6 -0
- package/dist/tools/validation.d.ts +10 -0
- package/dist/tools/validation.js +72 -0
- package/dist/tools/worktrees.d.ts +9 -0
- package/dist/tools/worktrees.js +63 -0
- package/dist/utils.d.ts +66 -0
- package/dist/utils.js +102 -0
- package/docs/TOOLS.md +55 -2
- package/package.json +5 -3
- package/scripts/generate-docs.ts +1 -1
- package/src/api-client/blockers.ts +86 -0
- package/src/api-client/cost.ts +185 -0
- package/src/api-client/decisions.ts +87 -0
- package/src/api-client/discovery.ts +81 -0
- package/src/api-client/ideas.ts +112 -0
- package/src/api-client/index.ts +378 -0
- package/src/api-client/project.ts +179 -0
- package/src/api-client/session.ts +220 -0
- package/src/api-client/tasks.ts +450 -0
- package/src/api-client/types.ts +32 -0
- package/src/api-client/worktrees.ts +53 -0
- package/src/api-client.test.ts +136 -9
- package/src/api-client.ts +125 -27
- package/src/cli-init.ts +504 -0
- package/src/handlers/__test-utils__.ts +2 -0
- package/src/handlers/cloud-agents.ts +138 -0
- package/src/handlers/discovery.ts +7 -0
- package/src/handlers/index.ts +3 -0
- package/src/handlers/session.ts +3 -1
- package/src/handlers/tasks.ts +10 -12
- package/src/handlers/tool-categories.test.ts +1 -1
- package/src/handlers/types.ts +2 -1
- package/src/handlers/validation.ts +6 -1
- package/src/index.test.ts +2 -2
- package/src/index.ts +8 -2
- package/src/token-tracking.ts +3 -2
- package/src/tools/blockers.ts +122 -0
- package/src/tools/bodies-of-work.ts +283 -0
- package/src/tools/cloud-agents.ts +70 -0
- package/src/tools/connectors.ts +191 -0
- package/src/tools/cost.ts +111 -0
- package/src/tools/decisions.ts +111 -0
- package/src/tools/deployment.ts +442 -0
- package/src/tools/discovery.ts +76 -0
- package/src/tools/fallback.ts +111 -0
- package/src/tools/file-checkouts.ts +145 -0
- package/src/tools/findings.ts +101 -0
- package/src/tools/git-issues.ts +130 -0
- package/src/tools/ideas.ts +162 -0
- package/src/tools/index.ts +131 -0
- package/src/tools/milestones.ts +118 -0
- package/src/tools/organizations.ts +224 -0
- package/src/tools/progress.ts +73 -0
- package/src/tools/project.ts +202 -0
- package/src/tools/requests.ts +68 -0
- package/src/tools/roles.ts +112 -0
- package/src/tools/session.ts +181 -0
- package/src/tools/sprints.ts +298 -0
- package/src/tools/tasks.ts +542 -0
- package/src/tools/tools.test.ts +222 -0
- package/src/tools/types.ts +9 -0
- package/src/tools/validation.ts +75 -0
- package/src/tools/worktrees.ts +66 -0
- package/src/tools.test.ts +1 -1
- package/src/utils.test.ts +229 -0
- package/src/utils.ts +117 -0
- package/dist/tools.d.ts +0 -2
- package/dist/tools.js +0 -3602
- package/src/tools.ts +0 -3607
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for split tool definitions
|
|
3
|
+
*
|
|
4
|
+
* Validates that domain-specific tool files:
|
|
5
|
+
* - Export valid Tool arrays
|
|
6
|
+
* - Have unique tool names
|
|
7
|
+
* - Have required inputSchema properties
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import { sessionTools } from './session.js';
|
|
12
|
+
import { costTools } from './cost.js';
|
|
13
|
+
import { discoveryTools } from './discovery.js';
|
|
14
|
+
import { worktreeTools } from './worktrees.js';
|
|
15
|
+
import { projectTools } from './project.js';
|
|
16
|
+
import { blockerTools } from './blockers.js';
|
|
17
|
+
import { decisionTools } from './decisions.js';
|
|
18
|
+
import { ideaTools } from './ideas.js';
|
|
19
|
+
import { buildToolList, toolCategories } from './index.js';
|
|
20
|
+
|
|
21
|
+
describe('Split Tool Definitions', () => {
|
|
22
|
+
describe('sessionTools', () => {
|
|
23
|
+
it('should export an array of tools', () => {
|
|
24
|
+
expect(Array.isArray(sessionTools)).toBe(true);
|
|
25
|
+
expect(sessionTools.length).toBeGreaterThan(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should include expected session tools', () => {
|
|
29
|
+
const toolNames = sessionTools.map(t => t.name);
|
|
30
|
+
expect(toolNames).toContain('start_work_session');
|
|
31
|
+
expect(toolNames).toContain('heartbeat');
|
|
32
|
+
expect(toolNames).toContain('end_work_session');
|
|
33
|
+
expect(toolNames).toContain('signal_idle');
|
|
34
|
+
expect(toolNames).toContain('get_token_usage');
|
|
35
|
+
expect(toolNames).toContain('report_token_usage');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should have valid inputSchema for all tools', () => {
|
|
39
|
+
for (const tool of sessionTools) {
|
|
40
|
+
expect(tool.inputSchema).toBeDefined();
|
|
41
|
+
expect(tool.inputSchema.type).toBe('object');
|
|
42
|
+
expect(tool.inputSchema.properties).toBeDefined();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('costTools', () => {
|
|
48
|
+
it('should export an array of tools', () => {
|
|
49
|
+
expect(Array.isArray(costTools)).toBe(true);
|
|
50
|
+
expect(costTools.length).toBeGreaterThan(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should include expected cost tools', () => {
|
|
54
|
+
const toolNames = costTools.map(t => t.name);
|
|
55
|
+
expect(toolNames).toContain('get_cost_summary');
|
|
56
|
+
expect(toolNames).toContain('add_cost_alert');
|
|
57
|
+
expect(toolNames).toContain('update_cost_alert');
|
|
58
|
+
expect(toolNames).toContain('delete_cost_alert');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('discoveryTools', () => {
|
|
63
|
+
it('should export an array of tools', () => {
|
|
64
|
+
expect(Array.isArray(discoveryTools)).toBe(true);
|
|
65
|
+
expect(discoveryTools.length).toBeGreaterThan(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should include expected discovery tools', () => {
|
|
69
|
+
const toolNames = discoveryTools.map(t => t.name);
|
|
70
|
+
expect(toolNames).toContain('query_knowledge_base');
|
|
71
|
+
expect(toolNames).toContain('discover_tools');
|
|
72
|
+
expect(toolNames).toContain('get_tool_info');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('worktreeTools', () => {
|
|
77
|
+
it('should export an array of tools', () => {
|
|
78
|
+
expect(Array.isArray(worktreeTools)).toBe(true);
|
|
79
|
+
expect(worktreeTools.length).toBeGreaterThan(0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should include expected worktree tools', () => {
|
|
83
|
+
const toolNames = worktreeTools.map(t => t.name);
|
|
84
|
+
expect(toolNames).toContain('get_stale_worktrees');
|
|
85
|
+
expect(toolNames).toContain('clear_worktree_path');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('projectTools', () => {
|
|
90
|
+
it('should export an array of tools', () => {
|
|
91
|
+
expect(Array.isArray(projectTools)).toBe(true);
|
|
92
|
+
expect(projectTools.length).toBeGreaterThan(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should include expected project tools', () => {
|
|
96
|
+
const toolNames = projectTools.map(t => t.name);
|
|
97
|
+
expect(toolNames).toContain('get_project_context');
|
|
98
|
+
expect(toolNames).toContain('create_project');
|
|
99
|
+
expect(toolNames).toContain('update_project');
|
|
100
|
+
expect(toolNames).toContain('get_project_summary');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('blockerTools', () => {
|
|
105
|
+
it('should export an array of tools', () => {
|
|
106
|
+
expect(Array.isArray(blockerTools)).toBe(true);
|
|
107
|
+
expect(blockerTools.length).toBeGreaterThan(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should include expected blocker tools', () => {
|
|
111
|
+
const toolNames = blockerTools.map(t => t.name);
|
|
112
|
+
expect(toolNames).toContain('add_blocker');
|
|
113
|
+
expect(toolNames).toContain('resolve_blocker');
|
|
114
|
+
expect(toolNames).toContain('get_blockers');
|
|
115
|
+
expect(toolNames).toContain('delete_blocker');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('decisionTools', () => {
|
|
120
|
+
it('should export an array of tools', () => {
|
|
121
|
+
expect(Array.isArray(decisionTools)).toBe(true);
|
|
122
|
+
expect(decisionTools.length).toBeGreaterThan(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should include expected decision tools', () => {
|
|
126
|
+
const toolNames = decisionTools.map(t => t.name);
|
|
127
|
+
expect(toolNames).toContain('log_decision');
|
|
128
|
+
expect(toolNames).toContain('get_decisions');
|
|
129
|
+
expect(toolNames).toContain('delete_decision');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('ideaTools', () => {
|
|
134
|
+
it('should export an array of tools', () => {
|
|
135
|
+
expect(Array.isArray(ideaTools)).toBe(true);
|
|
136
|
+
expect(ideaTools.length).toBeGreaterThan(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should include expected idea tools', () => {
|
|
140
|
+
const toolNames = ideaTools.map(t => t.name);
|
|
141
|
+
expect(toolNames).toContain('add_idea');
|
|
142
|
+
expect(toolNames).toContain('update_idea');
|
|
143
|
+
expect(toolNames).toContain('get_ideas');
|
|
144
|
+
expect(toolNames).toContain('convert_idea_to_task');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('buildToolList', () => {
|
|
149
|
+
it('should combine all domain tools', () => {
|
|
150
|
+
const allTools = buildToolList();
|
|
151
|
+
expect(Array.isArray(allTools)).toBe(true);
|
|
152
|
+
|
|
153
|
+
// Should include tools from all domains
|
|
154
|
+
const toolNames = allTools.map(t => t.name);
|
|
155
|
+
expect(toolNames).toContain('start_work_session');
|
|
156
|
+
expect(toolNames).toContain('get_cost_summary');
|
|
157
|
+
expect(toolNames).toContain('discover_tools');
|
|
158
|
+
expect(toolNames).toContain('get_stale_worktrees');
|
|
159
|
+
expect(toolNames).toContain('get_project_context');
|
|
160
|
+
expect(toolNames).toContain('add_blocker');
|
|
161
|
+
expect(toolNames).toContain('log_decision');
|
|
162
|
+
expect(toolNames).toContain('add_idea');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should have unique tool names', () => {
|
|
166
|
+
const allTools = buildToolList();
|
|
167
|
+
const toolNames = allTools.map(t => t.name);
|
|
168
|
+
const uniqueNames = new Set(toolNames);
|
|
169
|
+
expect(uniqueNames.size).toBe(toolNames.length);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('toolCategories', () => {
|
|
174
|
+
it('should have categories for all domains', () => {
|
|
175
|
+
expect(toolCategories.session).toBeDefined();
|
|
176
|
+
expect(toolCategories.cost).toBeDefined();
|
|
177
|
+
expect(toolCategories.discovery).toBeDefined();
|
|
178
|
+
expect(toolCategories.worktrees).toBeDefined();
|
|
179
|
+
expect(toolCategories.project).toBeDefined();
|
|
180
|
+
expect(toolCategories.blockers).toBeDefined();
|
|
181
|
+
expect(toolCategories.decisions).toBeDefined();
|
|
182
|
+
expect(toolCategories.ideas).toBeDefined();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should have correct tool names per category', () => {
|
|
186
|
+
expect(toolCategories.session).toContain('start_work_session');
|
|
187
|
+
expect(toolCategories.cost).toContain('get_cost_summary');
|
|
188
|
+
expect(toolCategories.discovery).toContain('discover_tools');
|
|
189
|
+
expect(toolCategories.worktrees).toContain('get_stale_worktrees');
|
|
190
|
+
expect(toolCategories.project).toContain('get_project_context');
|
|
191
|
+
expect(toolCategories.blockers).toContain('add_blocker');
|
|
192
|
+
expect(toolCategories.decisions).toContain('log_decision');
|
|
193
|
+
expect(toolCategories.ideas).toContain('add_idea');
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('tool structure validation', () => {
|
|
198
|
+
const allTools = buildToolList();
|
|
199
|
+
|
|
200
|
+
it('all tools should have a name', () => {
|
|
201
|
+
for (const tool of allTools) {
|
|
202
|
+
expect(tool.name).toBeDefined();
|
|
203
|
+
expect(typeof tool.name).toBe('string');
|
|
204
|
+
expect(tool.name.length).toBeGreaterThan(0);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('all tools should have a description', () => {
|
|
209
|
+
for (const tool of allTools) {
|
|
210
|
+
expect(tool.description).toBeDefined();
|
|
211
|
+
expect(typeof tool.description).toBe('string');
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('all tools should have an inputSchema', () => {
|
|
216
|
+
for (const tool of allTools) {
|
|
217
|
+
expect(tool.inputSchema).toBeDefined();
|
|
218
|
+
expect(tool.inputSchema.type).toBe('object');
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Tool Definitions
|
|
3
|
+
*
|
|
4
|
+
* Tools for task validation workflow:
|
|
5
|
+
* - get_tasks_awaiting_validation
|
|
6
|
+
* - claim_validation
|
|
7
|
+
* - validate_task
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Tool } from './types.js';
|
|
11
|
+
|
|
12
|
+
export const validationTools: Tool[] = [
|
|
13
|
+
{
|
|
14
|
+
name: 'get_tasks_awaiting_validation',
|
|
15
|
+
description: `Get completed tasks not yet validated.`,
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
project_id: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Project UUID',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
required: ['project_id'],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'claim_validation',
|
|
29
|
+
description: 'Claim a completed task for review. Shows "being reviewed" on dashboard.',
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
task_id: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Task UUID to claim for review',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: ['task_id'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'validate_task',
|
|
43
|
+
description: 'Validate a completed task. Include test results in validation_notes. For github-flow/git-flow projects, a PR must exist and checks must pass before approval.',
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
task_id: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Task UUID to validate',
|
|
50
|
+
},
|
|
51
|
+
validation_notes: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Notes about the validation (what was checked, any issues found)',
|
|
54
|
+
},
|
|
55
|
+
approved: {
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
description: 'Whether the task passes validation (true = approved, false = needs more work)',
|
|
58
|
+
},
|
|
59
|
+
pr_checks_passing: {
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
description: 'REQUIRED when approving tasks with PRs. Confirm you verified PR checks are passing via `gh pr view <PR_NUMBER> --json statusCheckRollup`. Set to true only if all required checks pass.',
|
|
62
|
+
},
|
|
63
|
+
skip_pr_check: {
|
|
64
|
+
type: 'boolean',
|
|
65
|
+
description: 'Skip PR existence check (use only for tasks that legitimately do not need a PR)',
|
|
66
|
+
},
|
|
67
|
+
create_fix_task: {
|
|
68
|
+
type: 'boolean',
|
|
69
|
+
description: 'When rejecting (approved: false), create a new fix task linked to the original. The fix task will contain the rejection reason and be assigned the same git branch.',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ['task_id', 'approved'],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Worktree Tool Definitions
|
|
3
|
+
*
|
|
4
|
+
* Tools for managing git worktrees:
|
|
5
|
+
* - get_stale_worktrees: Get worktrees that need cleanup
|
|
6
|
+
* - clear_worktree_path: Clear worktree_path from a task after cleanup
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Tool } from './types.js';
|
|
10
|
+
|
|
11
|
+
export const worktreeTools: Tool[] = [
|
|
12
|
+
{
|
|
13
|
+
name: 'get_stale_worktrees',
|
|
14
|
+
description: `Get worktrees that need cleanup.
|
|
15
|
+
|
|
16
|
+
Returns tasks with worktree_path set where:
|
|
17
|
+
- Task is completed or cancelled (worktree should have been cleaned up)
|
|
18
|
+
- Task has been abandoned (no activity for 24+ hours while in_progress)
|
|
19
|
+
|
|
20
|
+
IMPORTANT: Pass hostname (os.hostname()) to only see worktrees created on THIS machine.
|
|
21
|
+
Worktrees created on other machines cannot be removed locally.
|
|
22
|
+
|
|
23
|
+
For each stale worktree:
|
|
24
|
+
1. Run: git worktree remove <worktree_path>
|
|
25
|
+
2. Call: clear_worktree_path(task_id)`,
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
project_id: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Project UUID',
|
|
32
|
+
},
|
|
33
|
+
hostname: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Machine hostname (os.hostname()). Only returns worktrees created on this machine, preventing attempts to clean up worktrees on other machines.',
|
|
36
|
+
},
|
|
37
|
+
limit: {
|
|
38
|
+
type: 'number',
|
|
39
|
+
description: 'Max worktrees to return (default: 50)',
|
|
40
|
+
},
|
|
41
|
+
offset: {
|
|
42
|
+
type: 'number',
|
|
43
|
+
description: 'Number of worktrees to skip for pagination (default: 0)',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
required: ['project_id'],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'clear_worktree_path',
|
|
51
|
+
description: `Clear the worktree_path from a task after cleanup.
|
|
52
|
+
|
|
53
|
+
Call this AFTER removing the worktree with git worktree remove.
|
|
54
|
+
This marks the task as cleaned up so it won't appear in get_stale_worktrees.`,
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
task_id: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'Task UUID',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
required: ['task_id'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
];
|
package/src/tools.test.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { describe, it, expect } from 'vitest';
|
|
10
|
-
import { tools } from './tools.js';
|
|
10
|
+
import { tools } from './tools/index.js';
|
|
11
11
|
|
|
12
12
|
// Valid JSON Schema types
|
|
13
13
|
const VALID_SCHEMA_TYPES = ['string', 'number', 'boolean', 'object', 'array', 'null', 'integer'];
|
package/src/utils.test.ts
CHANGED
|
@@ -10,6 +10,11 @@ import {
|
|
|
10
10
|
isValidDeploymentStatusTransition,
|
|
11
11
|
extractProjectNameFromGitUrl,
|
|
12
12
|
normalizeGitUrl,
|
|
13
|
+
getErrorMessage,
|
|
14
|
+
hasErrorProperty,
|
|
15
|
+
extractApiError,
|
|
16
|
+
capPagination,
|
|
17
|
+
PAGINATION_LIMITS,
|
|
13
18
|
} from './utils.js';
|
|
14
19
|
|
|
15
20
|
// ============================================================================
|
|
@@ -783,3 +788,227 @@ describe('normalizeGitUrl', () => {
|
|
|
783
788
|
});
|
|
784
789
|
});
|
|
785
790
|
});
|
|
791
|
+
|
|
792
|
+
// ============================================================================
|
|
793
|
+
// Error Handling Utilities Tests
|
|
794
|
+
// ============================================================================
|
|
795
|
+
|
|
796
|
+
describe('getErrorMessage', () => {
|
|
797
|
+
it('extracts message from Error object', () => {
|
|
798
|
+
const error = new Error('Something went wrong');
|
|
799
|
+
expect(getErrorMessage(error)).toBe('Something went wrong');
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it('extracts message from TypeError', () => {
|
|
803
|
+
const error = new TypeError('Invalid type');
|
|
804
|
+
expect(getErrorMessage(error)).toBe('Invalid type');
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('returns string errors as-is', () => {
|
|
808
|
+
expect(getErrorMessage('Direct error string')).toBe('Direct error string');
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('converts numbers to string', () => {
|
|
812
|
+
expect(getErrorMessage(404)).toBe('404');
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('converts null to string', () => {
|
|
816
|
+
expect(getErrorMessage(null)).toBe('null');
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
it('converts undefined to string', () => {
|
|
820
|
+
expect(getErrorMessage(undefined)).toBe('undefined');
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
it('converts objects to string', () => {
|
|
824
|
+
expect(getErrorMessage({ foo: 'bar' })).toBe('[object Object]');
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it('handles empty Error message', () => {
|
|
828
|
+
const error = new Error('');
|
|
829
|
+
expect(getErrorMessage(error)).toBe('');
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
describe('hasErrorProperty', () => {
|
|
834
|
+
it('returns true for object with error property', () => {
|
|
835
|
+
expect(hasErrorProperty({ error: 'some error' })).toBe(true);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it('returns true for object with error as object', () => {
|
|
839
|
+
expect(hasErrorProperty({ error: { message: 'nested' } })).toBe(true);
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it('returns true for object with null error', () => {
|
|
843
|
+
expect(hasErrorProperty({ error: null })).toBe(true);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
it('returns false for null', () => {
|
|
847
|
+
expect(hasErrorProperty(null)).toBe(false);
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
it('returns false for undefined', () => {
|
|
851
|
+
expect(hasErrorProperty(undefined)).toBe(false);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it('returns false for string', () => {
|
|
855
|
+
expect(hasErrorProperty('error')).toBe(false);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
it('returns false for object without error property', () => {
|
|
859
|
+
expect(hasErrorProperty({ success: true })).toBe(false);
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it('returns false for empty object', () => {
|
|
863
|
+
expect(hasErrorProperty({})).toBe(false);
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
it('returns false for array', () => {
|
|
867
|
+
expect(hasErrorProperty(['error'])).toBe(false);
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
describe('extractApiError', () => {
|
|
872
|
+
it('extracts string error from response', () => {
|
|
873
|
+
expect(extractApiError({ error: 'Not found' })).toBe('Not found');
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it('extracts message from nested error object', () => {
|
|
877
|
+
expect(extractApiError({ error: { message: 'Validation failed' } })).toBe('Validation failed');
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it('returns "Unknown error" for non-string error without message', () => {
|
|
881
|
+
expect(extractApiError({ error: { code: 500 } })).toBe('Unknown error');
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
it('returns null for data without error property', () => {
|
|
885
|
+
expect(extractApiError({ success: true })).toBeNull();
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
it('returns null for null input', () => {
|
|
889
|
+
expect(extractApiError(null)).toBeNull();
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
it('returns null for undefined input', () => {
|
|
893
|
+
expect(extractApiError(undefined)).toBeNull();
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it('returns null for string input', () => {
|
|
897
|
+
expect(extractApiError('error')).toBeNull();
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it('returns null for empty object', () => {
|
|
901
|
+
expect(extractApiError({})).toBeNull();
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('handles empty string error', () => {
|
|
905
|
+
expect(extractApiError({ error: '' })).toBe('');
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
it('returns "Unknown error" for error as number', () => {
|
|
909
|
+
expect(extractApiError({ error: 404 })).toBe('Unknown error');
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
it('returns "Unknown error" for error as boolean', () => {
|
|
913
|
+
expect(extractApiError({ error: true })).toBe('Unknown error');
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it('handles deeply nested message', () => {
|
|
917
|
+
// Only extracts from immediate .message property
|
|
918
|
+
expect(extractApiError({ error: { nested: { message: 'deep' } } })).toBe('Unknown error');
|
|
919
|
+
});
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
// ============================================================================
|
|
923
|
+
// Pagination Utilities Tests
|
|
924
|
+
// ============================================================================
|
|
925
|
+
|
|
926
|
+
describe('PAGINATION_LIMITS', () => {
|
|
927
|
+
it('should have expected default values', () => {
|
|
928
|
+
expect(PAGINATION_LIMITS.DEFAULT_MAX_LIMIT).toBe(50);
|
|
929
|
+
expect(PAGINATION_LIMITS.DEFAULT_LIMIT).toBe(20);
|
|
930
|
+
expect(PAGINATION_LIMITS.TASK_LIMIT).toBe(20);
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
describe('capPagination', () => {
|
|
935
|
+
describe('limit capping', () => {
|
|
936
|
+
it('should cap limit to maxLimit when exceeded', () => {
|
|
937
|
+
const { cappedLimit } = capPagination(100, 0, 50);
|
|
938
|
+
expect(cappedLimit).toBe(50);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it('should use limit when under maxLimit', () => {
|
|
942
|
+
const { cappedLimit } = capPagination(25, 0, 50);
|
|
943
|
+
expect(cappedLimit).toBe(25);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
it('should use default maxLimit when not specified', () => {
|
|
947
|
+
const { cappedLimit } = capPagination(100, 0);
|
|
948
|
+
expect(cappedLimit).toBe(PAGINATION_LIMITS.DEFAULT_MAX_LIMIT);
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
it('should enforce minimum limit of 1', () => {
|
|
952
|
+
const { cappedLimit } = capPagination(0, 0, 50);
|
|
953
|
+
expect(cappedLimit).toBe(1);
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
it('should handle negative limit', () => {
|
|
957
|
+
const { cappedLimit } = capPagination(-5, 0, 50);
|
|
958
|
+
expect(cappedLimit).toBe(1);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
it('should use DEFAULT_LIMIT when limit is undefined', () => {
|
|
962
|
+
const { cappedLimit } = capPagination(undefined, 0, 50);
|
|
963
|
+
expect(cappedLimit).toBe(PAGINATION_LIMITS.DEFAULT_LIMIT);
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
describe('offset safety', () => {
|
|
968
|
+
it('should keep positive offset unchanged', () => {
|
|
969
|
+
const { safeOffset } = capPagination(10, 50, 50);
|
|
970
|
+
expect(safeOffset).toBe(50);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
it('should convert negative offset to 0', () => {
|
|
974
|
+
const { safeOffset } = capPagination(10, -10, 50);
|
|
975
|
+
expect(safeOffset).toBe(0);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
it('should handle zero offset', () => {
|
|
979
|
+
const { safeOffset } = capPagination(10, 0, 50);
|
|
980
|
+
expect(safeOffset).toBe(0);
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
it('should use 0 when offset is undefined', () => {
|
|
984
|
+
const { safeOffset } = capPagination(10, undefined, 50);
|
|
985
|
+
expect(safeOffset).toBe(0);
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
describe('combined behavior', () => {
|
|
990
|
+
it('should return both cappedLimit and safeOffset', () => {
|
|
991
|
+
const result = capPagination(100, -5, 20);
|
|
992
|
+
expect(result).toEqual({
|
|
993
|
+
cappedLimit: 20,
|
|
994
|
+
safeOffset: 0,
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
it('should work with task limit constant', () => {
|
|
999
|
+
const result = capPagination(50, 10, PAGINATION_LIMITS.TASK_LIMIT);
|
|
1000
|
+
expect(result).toEqual({
|
|
1001
|
+
cappedLimit: 20,
|
|
1002
|
+
safeOffset: 10,
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it('should handle all undefined values', () => {
|
|
1007
|
+
const result = capPagination(undefined, undefined);
|
|
1008
|
+
expect(result).toEqual({
|
|
1009
|
+
cappedLimit: PAGINATION_LIMITS.DEFAULT_LIMIT,
|
|
1010
|
+
safeOffset: 0,
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
});
|