@achieveai/azuredevops-mcp 1.3.17 → 1.3.18
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/Interfaces/Common.js +10 -1
- package/dist/Interfaces/Common.js.map +1 -1
- package/dist/Services/AzureDevOpsService.js +234 -32
- package/dist/Services/AzureDevOpsService.js.map +1 -1
- package/dist/Services/BoardsSprintsService.js +105 -13
- package/dist/Services/BoardsSprintsService.js.map +1 -1
- package/dist/Services/BuildService.js +151 -24
- package/dist/Services/BuildService.js.map +1 -1
- package/dist/Services/GitService.js +26 -3
- package/dist/Services/GitService.js.map +1 -1
- package/dist/Services/ProjectService.js +47 -6
- package/dist/Services/ProjectService.js.map +1 -1
- package/dist/Services/WorkItemService.js +183 -142
- package/dist/Services/WorkItemService.js.map +1 -1
- package/dist/Tools/BoardsSprintsTools.js +2 -8
- package/dist/Tools/BoardsSprintsTools.js.map +1 -1
- package/dist/Tools/BuildTools.js +5 -8
- package/dist/Tools/BuildTools.js.map +1 -1
- package/dist/Tools/WorkItemTools.js +111 -74
- package/dist/Tools/WorkItemTools.js.map +1 -1
- package/dist/index.js +22 -12
- package/dist/index.js.map +1 -1
- package/dist/utils/formatHelpers.js +15 -0
- package/dist/utils/formatHelpers.js.map +1 -1
- package/package.json +2 -2
- package/dist/Services/BuildService.project.test.js +0 -91
- package/dist/Services/BuildService.project.test.js.map +0 -1
- package/dist/Services/GitService.project.test.js +0 -407
- package/dist/Services/GitService.project.test.js.map +0 -1
- package/dist/package.json +0 -59
- package/dist/src/Interfaces/AIAssisted.js +0 -3
- package/dist/src/Interfaces/AIAssisted.js.map +0 -1
- package/dist/src/Interfaces/ArtifactManagement.js +0 -3
- package/dist/src/Interfaces/ArtifactManagement.js.map +0 -1
- package/dist/src/Interfaces/AzureDevOps.js +0 -3
- package/dist/src/Interfaces/AzureDevOps.js.map +0 -1
- package/dist/src/Interfaces/BoardsAndSprints.js +0 -3
- package/dist/src/Interfaces/BoardsAndSprints.js.map +0 -1
- package/dist/src/Interfaces/CodeAndRepositories.js +0 -3
- package/dist/src/Interfaces/CodeAndRepositories.js.map +0 -1
- package/dist/src/Interfaces/Common.js +0 -134
- package/dist/src/Interfaces/Common.js.map +0 -1
- package/dist/src/Interfaces/CostResourceManagement.js +0 -3
- package/dist/src/Interfaces/CostResourceManagement.js.map +0 -1
- package/dist/src/Interfaces/DevSecOps.js +0 -3
- package/dist/src/Interfaces/DevSecOps.js.map +0 -1
- package/dist/src/Interfaces/ExternalIntegrations.js +0 -3
- package/dist/src/Interfaces/ExternalIntegrations.js.map +0 -1
- package/dist/src/Interfaces/HybridCrossPlatform.js +0 -3
- package/dist/src/Interfaces/HybridCrossPlatform.js.map +0 -1
- package/dist/src/Interfaces/Pipelines.js +0 -3
- package/dist/src/Interfaces/Pipelines.js.map +0 -1
- package/dist/src/Interfaces/ProjectManagement.js +0 -3
- package/dist/src/Interfaces/ProjectManagement.js.map +0 -1
- package/dist/src/Interfaces/TestingCapabilities.js +0 -3
- package/dist/src/Interfaces/TestingCapabilities.js.map +0 -1
- package/dist/src/Interfaces/Wiki.js +0 -3
- package/dist/src/Interfaces/Wiki.js.map +0 -1
- package/dist/src/Interfaces/WorkItems.js +0 -3
- package/dist/src/Interfaces/WorkItems.js.map +0 -1
- package/dist/src/Services/AIAssistedDevelopmentService.js +0 -195
- package/dist/src/Services/AIAssistedDevelopmentService.js.map +0 -1
- package/dist/src/Services/ArtifactManagementService.js +0 -346
- package/dist/src/Services/ArtifactManagementService.js.map +0 -1
- package/dist/src/Services/AzureDevOpsService.js +0 -385
- package/dist/src/Services/AzureDevOpsService.js.map +0 -1
- package/dist/src/Services/BoardsSprintsService.js +0 -339
- package/dist/src/Services/BoardsSprintsService.js.map +0 -1
- package/dist/src/Services/BuildService.js +0 -405
- package/dist/src/Services/BuildService.js.map +0 -1
- package/dist/src/Services/DevSecOpsService.js +0 -307
- package/dist/src/Services/DevSecOpsService.js.map +0 -1
- package/dist/src/Services/EntraAuthHandler.js +0 -337
- package/dist/src/Services/EntraAuthHandler.js.map +0 -1
- package/dist/src/Services/GitService.js +0 -1595
- package/dist/src/Services/GitService.js.map +0 -1
- package/dist/src/Services/ProjectService.js +0 -257
- package/dist/src/Services/ProjectService.js.map +0 -1
- package/dist/src/Services/TestingCapabilitiesService.js +0 -149
- package/dist/src/Services/TestingCapabilitiesService.js.map +0 -1
- package/dist/src/Services/WikiService.js +0 -90
- package/dist/src/Services/WikiService.js.map +0 -1
- package/dist/src/Services/WorkItemService.js +0 -885
- package/dist/src/Services/WorkItemService.js.map +0 -1
- package/dist/src/Tools/AIAssistedDevelopmentTools.js +0 -137
- package/dist/src/Tools/AIAssistedDevelopmentTools.js.map +0 -1
- package/dist/src/Tools/ArtifactManagementTools.js +0 -140
- package/dist/src/Tools/ArtifactManagementTools.js.map +0 -1
- package/dist/src/Tools/BoardsSprintsTools.js +0 -338
- package/dist/src/Tools/BoardsSprintsTools.js.map +0 -1
- package/dist/src/Tools/BuildTools.js +0 -468
- package/dist/src/Tools/BuildTools.js.map +0 -1
- package/dist/src/Tools/DevSecOpsTools.js +0 -147
- package/dist/src/Tools/DevSecOpsTools.js.map +0 -1
- package/dist/src/Tools/GitTools.js +0 -1475
- package/dist/src/Tools/GitTools.js.map +0 -1
- package/dist/src/Tools/ProjectTools.js +0 -360
- package/dist/src/Tools/ProjectTools.js.map +0 -1
- package/dist/src/Tools/TestingCapabilitiesTools.js +0 -157
- package/dist/src/Tools/TestingCapabilitiesTools.js.map +0 -1
- package/dist/src/Tools/WikiTools.js +0 -137
- package/dist/src/Tools/WikiTools.js.map +0 -1
- package/dist/src/Tools/WorkItemTools.js +0 -862
- package/dist/src/Tools/WorkItemTools.js.map +0 -1
- package/dist/src/config.js +0 -176
- package/dist/src/config.js.map +0 -1
- package/dist/src/index.js +0 -1716
- package/dist/src/index.js.map +0 -1
- package/dist/src/utils/formatHelpers.js +0 -257
- package/dist/src/utils/formatHelpers.js.map +0 -1
- package/dist/src/utils/getClassMethods.js +0 -8
- package/dist/src/utils/getClassMethods.js.map +0 -1
- package/dist/src/utils/repositoryResolver.js +0 -40
- package/dist/src/utils/repositoryResolver.js.map +0 -1
|
@@ -1,862 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.WorkItemToolMethods = exports.WorkItemTools = void 0;
|
|
7
|
-
const WorkItemService_1 = require("../Services/WorkItemService");
|
|
8
|
-
const GitService_1 = require("../Services/GitService");
|
|
9
|
-
const Common_1 = require("../Interfaces/Common");
|
|
10
|
-
const getClassMethods_1 = __importDefault(require("../utils/getClassMethods"));
|
|
11
|
-
const formatHelpers_1 = require("../utils/formatHelpers");
|
|
12
|
-
/**
|
|
13
|
-
* Parse a target ID string with optional prefix into a typed target.
|
|
14
|
-
* Supports: PR#123, BUILD#456, BRANCH#main, COMMIT#abc, WI#789, or plain "789"
|
|
15
|
-
*/
|
|
16
|
-
function parseTargetId(targetId) {
|
|
17
|
-
const trimmed = targetId.trim();
|
|
18
|
-
const match = trimmed.match(/^(PR|BUILD|BRANCH|COMMIT|WI)#(.+)$/i);
|
|
19
|
-
if (!match) {
|
|
20
|
-
// Plain number = work item
|
|
21
|
-
return { type: 'workitem', id: trimmed, displayName: 'Work Item' };
|
|
22
|
-
}
|
|
23
|
-
const prefix = match[1].toUpperCase();
|
|
24
|
-
const id = match[2];
|
|
25
|
-
const prefixMap = {
|
|
26
|
-
'PR': { type: 'pr', displayName: 'Pull Request' },
|
|
27
|
-
'BUILD': { type: 'build', displayName: 'Build' },
|
|
28
|
-
'BRANCH': { type: 'branch', displayName: 'Branch' },
|
|
29
|
-
'COMMIT': { type: 'commit', displayName: 'Commit' },
|
|
30
|
-
'WI': { type: 'workitem', displayName: 'Work Item' },
|
|
31
|
-
};
|
|
32
|
-
return { ...prefixMap[prefix], id };
|
|
33
|
-
}
|
|
34
|
-
class WorkItemTools {
|
|
35
|
-
constructor(config) {
|
|
36
|
-
this.config = config;
|
|
37
|
-
this.workItemService = new WorkItemService_1.WorkItemService(config);
|
|
38
|
-
this.gitService = new GitService_1.GitService(config);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Resolve project name to project ID (GUID) using CoreApi
|
|
42
|
-
*/
|
|
43
|
-
async resolveProjectId() {
|
|
44
|
-
const coreApi = await this.workItemService['connection'].getCoreApi();
|
|
45
|
-
const project = await coreApi.getProject(this.config.project);
|
|
46
|
-
if (!project?.id) {
|
|
47
|
-
throw new Error(`Could not resolve project '${this.config.project}' to a GUID.`);
|
|
48
|
-
}
|
|
49
|
-
return project.id;
|
|
50
|
-
}
|
|
51
|
-
buildSummaryTable(workItems, includeChanged = false) {
|
|
52
|
-
const headers = ['ID', 'Title', 'Type', 'State', 'Assigned', 'Author', 'Area', 'Team'];
|
|
53
|
-
const rows = workItems.map((workItem) => {
|
|
54
|
-
const row = [
|
|
55
|
-
`#${workItem.id || 'N/A'}`,
|
|
56
|
-
(0, formatHelpers_1.truncateText)(workItem.title || '-', 36),
|
|
57
|
-
`${(0, formatHelpers_1.getWorkItemTypeEmoji)(workItem.workItemType || '')} ${workItem.workItemType || '-'}`,
|
|
58
|
-
`${(0, formatHelpers_1.getStateEmoji)(workItem.state || '')} ${workItem.state || '-'}`,
|
|
59
|
-
(0, formatHelpers_1.truncateText)(workItem.assignedTo?.displayName || 'Unassigned', 18),
|
|
60
|
-
(0, formatHelpers_1.truncateText)(workItem.createdBy?.displayName || 'Unknown', 18),
|
|
61
|
-
(0, formatHelpers_1.truncateText)(workItem.areaPath || '-', 22),
|
|
62
|
-
(0, formatHelpers_1.truncateText)(workItem.teamProject || '-', 16),
|
|
63
|
-
];
|
|
64
|
-
if (includeChanged) {
|
|
65
|
-
row.push(workItem.changedDate ? (0, formatHelpers_1.formatRelativeDate)(workItem.changedDate) : '-');
|
|
66
|
-
}
|
|
67
|
-
return row;
|
|
68
|
-
});
|
|
69
|
-
if (includeChanged) {
|
|
70
|
-
headers.push('Changed');
|
|
71
|
-
}
|
|
72
|
-
return (0, formatHelpers_1.markdownTable)(headers, rows);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* List work items based on a WIQL query
|
|
76
|
-
*/
|
|
77
|
-
async listWorkItems(params) {
|
|
78
|
-
try {
|
|
79
|
-
const response = await this.workItemService.listWorkItems(params.query, params.top, params.days, params.fields);
|
|
80
|
-
const items = response.workItems || [];
|
|
81
|
-
if (items.length === 0) {
|
|
82
|
-
let md = `## Work Items\n\nNo work items found for the given WIQL query.`;
|
|
83
|
-
if (response.recentDaysApplied) {
|
|
84
|
-
md += `\n\nScoped to the last **${response.recentDaysApplied} day${response.recentDaysApplied === 1 ? '' : 's'}** by default.`;
|
|
85
|
-
}
|
|
86
|
-
md += `\n\nCheck your query syntax, widen the date window, or broaden the filter criteria.`;
|
|
87
|
-
return (0, Common_1.formatMcpResponse)(response, md);
|
|
88
|
-
}
|
|
89
|
-
let md = `## Work Items\n\n**${items.length} item${items.length !== 1 ? 's' : ''}** from WIQL query\n\n`;
|
|
90
|
-
if (response.recentDaysApplied) {
|
|
91
|
-
md += `Scoped to the last **${response.recentDaysApplied} day${response.recentDaysApplied === 1 ? '' : 's'}** by default.\n\n`;
|
|
92
|
-
}
|
|
93
|
-
md += this.buildSummaryTable(items);
|
|
94
|
-
if (response.effectiveQuery) {
|
|
95
|
-
md += `\n\n**Effective WIQL**\n\n\`\`\`sql\n${response.effectiveQuery}\n\`\`\``;
|
|
96
|
-
}
|
|
97
|
-
md += `\n\nUse \`getWorkItemsBatch\` with selected IDs and fields when you need details.`;
|
|
98
|
-
return (0, Common_1.formatMcpResponse)(response, md, false, true);
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
console.error('Error in listWorkItems tool:', error);
|
|
102
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Get a work item by ID
|
|
107
|
-
*/
|
|
108
|
-
async getWorkItemById(params) {
|
|
109
|
-
try {
|
|
110
|
-
const workItem = await this.workItemService.getWorkItemWithEffortRollup(params);
|
|
111
|
-
return this.formatWorkItemResponse(workItem, params.fullDescription);
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
console.error('Error in getWorkItemById tool:', error);
|
|
115
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Format work item response with optimized token usage
|
|
120
|
-
*/
|
|
121
|
-
formatWorkItemResponse(workItem, fullDescription) {
|
|
122
|
-
if (!workItem) {
|
|
123
|
-
return {
|
|
124
|
-
content: [
|
|
125
|
-
{
|
|
126
|
-
type: "text",
|
|
127
|
-
text: "Work item not found."
|
|
128
|
-
}
|
|
129
|
-
]
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
// Helper function to parse and format description
|
|
133
|
-
const formatDescription = (description) => {
|
|
134
|
-
if (!description)
|
|
135
|
-
return 'No description provided';
|
|
136
|
-
const cleanDesc = (0, formatHelpers_1.stripHtml)(description);
|
|
137
|
-
if (fullDescription)
|
|
138
|
-
return cleanDesc;
|
|
139
|
-
return cleanDesc.length > 300 ? cleanDesc.substring(0, 300) + '...' : cleanDesc;
|
|
140
|
-
};
|
|
141
|
-
// Helper function to format sprint information (inline)
|
|
142
|
-
const formatSprintInfo = (iterationPath) => {
|
|
143
|
-
if (!iterationPath)
|
|
144
|
-
return 'No sprint';
|
|
145
|
-
const parts = iterationPath.split('\\');
|
|
146
|
-
const sprint = parts[parts.length - 1];
|
|
147
|
-
return sprint;
|
|
148
|
-
};
|
|
149
|
-
// Generate the main work item display with summary at top
|
|
150
|
-
const emoji = (0, formatHelpers_1.getWorkItemTypeEmoji)(workItem.workItemType);
|
|
151
|
-
const stateEmoji = (0, formatHelpers_1.getStateEmoji)(workItem.state);
|
|
152
|
-
const priorityEmoji = (0, formatHelpers_1.getPriorityEmoji)(workItem.priority);
|
|
153
|
-
let result = `## Work Item #${workItem.id}\n\n`;
|
|
154
|
-
// One-line summary with key info
|
|
155
|
-
result += `**${emoji} ${workItem.workItemType}** | ${stateEmoji} ${workItem.state} | ${priorityEmoji} P${workItem.priority || '?'} | `;
|
|
156
|
-
result += `Assigned: ${workItem.assignedTo?.displayName || 'Unassigned'} | `;
|
|
157
|
-
result += `${formatSprintInfo(workItem.iterationPath)}\n\n`;
|
|
158
|
-
result += `# ${workItem.title}\n\n`;
|
|
159
|
-
// Metadata: one-line format instead of table
|
|
160
|
-
const createdBy = workItem.createdBy?.displayName || 'Unknown';
|
|
161
|
-
const createdDate = workItem.createdDate ? `${(0, formatHelpers_1.formatFullDate)(workItem.createdDate)} (${(0, formatHelpers_1.formatRelativeDate)(workItem.createdDate)})` : 'Not set';
|
|
162
|
-
const updatedDate = workItem.changedDate ? `${(0, formatHelpers_1.formatFullDate)(workItem.changedDate)} (${(0, formatHelpers_1.formatRelativeDate)(workItem.changedDate)})` : 'Not set';
|
|
163
|
-
result += `**Area:** ${workItem.areaPath || 'Not set'} | **Created:** ${createdDate} by ${createdBy} | **Updated:** ${updatedDate}\n\n`;
|
|
164
|
-
result += `---\n\n`;
|
|
165
|
-
// Effort Tracking Section (condensed from 3 tables to inline stats)
|
|
166
|
-
const hasEffortData = workItem.originalEstimate || workItem.completedWork || workItem.remainingWork;
|
|
167
|
-
const hasChildEffort = workItem.childEffortRollup;
|
|
168
|
-
if (hasEffortData || hasChildEffort) {
|
|
169
|
-
result += `### Effort\n\n`;
|
|
170
|
-
if (hasEffortData) {
|
|
171
|
-
const original = workItem.originalEstimate || 0;
|
|
172
|
-
const completed = workItem.completedWork || 0;
|
|
173
|
-
const remaining = workItem.remainingWork || 0;
|
|
174
|
-
const percentage = original > 0 ? Math.round((completed / original) * 100) : 0;
|
|
175
|
-
result += `**Direct:** ${(0, formatHelpers_1.formatEffort)(completed)}/${(0, formatHelpers_1.formatEffort)(original)} done (${percentage}%) | ${(0, formatHelpers_1.formatEffort)(remaining)} remaining\n`;
|
|
176
|
-
}
|
|
177
|
-
if (hasChildEffort) {
|
|
178
|
-
const rollup = workItem.childEffortRollup;
|
|
179
|
-
const childPercentage = rollup.totalOriginalEstimate > 0
|
|
180
|
-
? Math.round((rollup.totalCompletedWork / rollup.totalOriginalEstimate) * 100)
|
|
181
|
-
: 0;
|
|
182
|
-
result += `**Children (${rollup.childCount}):** ${(0, formatHelpers_1.formatEffort)(rollup.totalCompletedWork)}/${(0, formatHelpers_1.formatEffort)(rollup.totalOriginalEstimate)} done (${childPercentage}%) | ${(0, formatHelpers_1.formatEffort)(rollup.totalRemainingWork)} remaining\n`;
|
|
183
|
-
}
|
|
184
|
-
// Combined summary if both exist
|
|
185
|
-
if (hasEffortData && hasChildEffort) {
|
|
186
|
-
const combinedOriginal = (workItem.originalEstimate || 0) + workItem.childEffortRollup.totalOriginalEstimate;
|
|
187
|
-
const combinedCompleted = (workItem.completedWork || 0) + workItem.childEffortRollup.totalCompletedWork;
|
|
188
|
-
const combinedRemaining = (workItem.remainingWork || 0) + workItem.childEffortRollup.totalRemainingWork;
|
|
189
|
-
const combinedPercentage = combinedOriginal > 0
|
|
190
|
-
? Math.round((combinedCompleted / combinedOriginal) * 100)
|
|
191
|
-
: 0;
|
|
192
|
-
result += `**Total:** ${(0, formatHelpers_1.formatEffort)(combinedCompleted)}/${(0, formatHelpers_1.formatEffort)(combinedOriginal)} done (${combinedPercentage}%) | ${(0, formatHelpers_1.formatEffort)(combinedRemaining)} remaining\n`;
|
|
193
|
-
}
|
|
194
|
-
result += `\n---\n\n`;
|
|
195
|
-
}
|
|
196
|
-
// Relationships section (concise list instead of detailed breakdown)
|
|
197
|
-
if (workItem.relations && workItem.relations.length > 0) {
|
|
198
|
-
result += `### Related Items\n\n`;
|
|
199
|
-
// Separate work item links from artifact links
|
|
200
|
-
const wiRelations = workItem.relations.filter((r) => r.relationshipType !== 'ArtifactLink');
|
|
201
|
-
const artifactRelations = workItem.relations.filter((r) => r.relationshipType === 'ArtifactLink');
|
|
202
|
-
// Group work item links by relationship type
|
|
203
|
-
if (wiRelations.length > 0) {
|
|
204
|
-
const grouped = {};
|
|
205
|
-
wiRelations.forEach((relation) => {
|
|
206
|
-
const relType = relation.relationshipType || 'Related';
|
|
207
|
-
if (!grouped[relType])
|
|
208
|
-
grouped[relType] = [];
|
|
209
|
-
grouped[relType].push(relation.relatedWorkItemId);
|
|
210
|
-
});
|
|
211
|
-
const relationTypes = {
|
|
212
|
-
'System.LinkTypes.Hierarchy-Forward': 'Child',
|
|
213
|
-
'System.LinkTypes.Hierarchy-Reverse': 'Parent',
|
|
214
|
-
'System.LinkTypes.Dependency-Forward': 'Successor',
|
|
215
|
-
'System.LinkTypes.Dependency-Reverse': 'Predecessor',
|
|
216
|
-
'System.LinkTypes.Related': 'Related'
|
|
217
|
-
};
|
|
218
|
-
Object.entries(grouped).forEach(([relType, ids]) => {
|
|
219
|
-
const label = relationTypes[relType]?.split(' ')[1] || 'Related';
|
|
220
|
-
const idList = ids.map(id => `#${id}`).join(', ');
|
|
221
|
-
result += `- ${idList} (${label})\n`;
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
// Show artifact links
|
|
225
|
-
if (artifactRelations.length > 0) {
|
|
226
|
-
const artifactEmojis = {
|
|
227
|
-
'Pull Request': 'PR',
|
|
228
|
-
'Build': 'Build',
|
|
229
|
-
'Branch': 'Branch',
|
|
230
|
-
'Commit': 'Commit',
|
|
231
|
-
};
|
|
232
|
-
artifactRelations.forEach((relation) => {
|
|
233
|
-
const displayName = relation.artifactDisplayName || relation.artifactType || 'Artifact';
|
|
234
|
-
const prefix = artifactEmojis[relation.artifactType] || 'Artifact';
|
|
235
|
-
result += `- ${prefix} ${displayName} #${relation.artifactId} (${relation.artifactType})\n`;
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
result += `\n---\n\n`;
|
|
239
|
-
}
|
|
240
|
-
// Description section
|
|
241
|
-
result += `## Description\n\n`;
|
|
242
|
-
result += `${formatDescription(workItem.description)}\n\n`;
|
|
243
|
-
// Prepare structured content
|
|
244
|
-
const structuredData = {
|
|
245
|
-
id: workItem.id,
|
|
246
|
-
type: workItem.workItemType,
|
|
247
|
-
title: workItem.title,
|
|
248
|
-
state: workItem.state,
|
|
249
|
-
priority: workItem.priority,
|
|
250
|
-
assignedTo: workItem.assignedTo?.displayName,
|
|
251
|
-
createdBy: workItem.createdBy?.displayName,
|
|
252
|
-
createdDate: workItem.createdDate,
|
|
253
|
-
changedDate: workItem.changedDate,
|
|
254
|
-
areaPath: workItem.areaPath,
|
|
255
|
-
iterationPath: workItem.iterationPath,
|
|
256
|
-
effort: {
|
|
257
|
-
original: workItem.originalEstimate,
|
|
258
|
-
completed: workItem.completedWork,
|
|
259
|
-
remaining: workItem.remainingWork
|
|
260
|
-
},
|
|
261
|
-
childEffort: hasChildEffort ? {
|
|
262
|
-
childCount: workItem.childEffortRollup.childCount,
|
|
263
|
-
totalOriginal: workItem.childEffortRollup.totalOriginalEstimate,
|
|
264
|
-
totalCompleted: workItem.childEffortRollup.totalCompletedWork,
|
|
265
|
-
totalRemaining: workItem.childEffortRollup.totalRemainingWork
|
|
266
|
-
} : null,
|
|
267
|
-
relations: workItem.relations?.map((r) => {
|
|
268
|
-
if (r.relationshipType === 'ArtifactLink') {
|
|
269
|
-
return {
|
|
270
|
-
type: r.relationshipType,
|
|
271
|
-
artifactType: r.artifactType,
|
|
272
|
-
artifactId: r.artifactId,
|
|
273
|
-
artifactDisplayName: r.artifactDisplayName,
|
|
274
|
-
artifactUri: r.artifactUri,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
return {
|
|
278
|
-
type: r.relationshipType,
|
|
279
|
-
relatedId: r.relatedWorkItemId,
|
|
280
|
-
};
|
|
281
|
-
}) || [],
|
|
282
|
-
description: workItem.description
|
|
283
|
-
};
|
|
284
|
-
return (0, Common_1.formatMcpResponse)(structuredData, result, false, true);
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Search work items
|
|
288
|
-
*/
|
|
289
|
-
async searchWorkItems(params) {
|
|
290
|
-
try {
|
|
291
|
-
const results = await this.workItemService.searchWorkItems(params);
|
|
292
|
-
return this.formatSearchResultsResponse(results);
|
|
293
|
-
}
|
|
294
|
-
catch (error) {
|
|
295
|
-
console.error('Error in searchWorkItems tool:', error);
|
|
296
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Format search results response with optimized tabular view
|
|
301
|
-
*/
|
|
302
|
-
formatSearchResultsResponse(results) {
|
|
303
|
-
if (!results || !results.workItems || results.workItems.length === 0) {
|
|
304
|
-
const recentWindow = results?.recentDaysApplied ? ` in the last ${results.recentDaysApplied} day${results.recentDaysApplied === 1 ? '' : 's'}` : '';
|
|
305
|
-
const wiqlBlock = results?.wiql ? `\n\n**Generated WIQL**\n\n\`\`\`sql\n${results.wiql}\n\`\`\`` : '';
|
|
306
|
-
return {
|
|
307
|
-
content: [
|
|
308
|
-
{
|
|
309
|
-
type: "text",
|
|
310
|
-
text: `## Search Results\n\nNo work items found matching "${results?.searchQuery || 'your search'}"${recentWindow}.\n\nContains-based search is expensive in Azure DevOps. Prefer focused WIQL via \`listWorkItems\` or a saved query when possible.${wiqlBlock}`
|
|
311
|
-
}
|
|
312
|
-
]
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
// Calculate summary statistics upfront
|
|
316
|
-
const typeSummary = results.workItems.reduce((acc, item) => {
|
|
317
|
-
acc[item.workItemType] = (acc[item.workItemType] || 0) + 1;
|
|
318
|
-
return acc;
|
|
319
|
-
}, {});
|
|
320
|
-
const stateSummary = results.workItems.reduce((acc, item) => {
|
|
321
|
-
acc[item.state] = (acc[item.state] || 0) + 1;
|
|
322
|
-
return acc;
|
|
323
|
-
}, {});
|
|
324
|
-
const totalEffort = {
|
|
325
|
-
original: results.workItems.reduce((sum, i) => sum + (i.originalEstimate || 0), 0),
|
|
326
|
-
completed: results.workItems.reduce((sum, i) => sum + (i.completedWork || 0), 0)
|
|
327
|
-
};
|
|
328
|
-
// Compact type/status lists
|
|
329
|
-
const typeList = Object.entries(typeSummary)
|
|
330
|
-
.map(([type, count]) => `${count} ${type.toLowerCase()}${count === 1 ? '' : 's'}`)
|
|
331
|
-
.join(', ');
|
|
332
|
-
const statusList = Object.entries(stateSummary)
|
|
333
|
-
.map(([state, count]) => `${count} ${state.toLowerCase()}`)
|
|
334
|
-
.join(', ');
|
|
335
|
-
// START WITH SUMMARY AT TOP
|
|
336
|
-
let result = `## Search Results: "${results.searchQuery}"\n\n`;
|
|
337
|
-
result += `**${results.totalResults} items** | ${typeList} | ${statusList}`;
|
|
338
|
-
if (totalEffort.completed > 0) {
|
|
339
|
-
result += ` | **${(0, formatHelpers_1.formatEffort)(totalEffort.completed)}/${(0, formatHelpers_1.formatEffort)(totalEffort.original)}** completed`;
|
|
340
|
-
}
|
|
341
|
-
result += `\n\n---\n\n`;
|
|
342
|
-
result += `| ID | Title | Type | Status | Assigned | Author | Area | Team |\n`;
|
|
343
|
-
result += `|----|-------|------|--------|----------|--------|------|------|\n`;
|
|
344
|
-
results.workItems.forEach((workItem) => {
|
|
345
|
-
const typeEmoji = (0, formatHelpers_1.getWorkItemTypeEmoji)(workItem.workItemType);
|
|
346
|
-
const stateEmoji = (0, formatHelpers_1.getStateEmoji)(workItem.state);
|
|
347
|
-
const assignedTo = (0, formatHelpers_1.truncateText)(workItem.assignedTo?.displayName || 'Unassigned', 18);
|
|
348
|
-
const createdBy = (0, formatHelpers_1.truncateText)(workItem.createdBy?.displayName || 'Unknown', 18);
|
|
349
|
-
const areaPath = (0, formatHelpers_1.truncateText)(workItem.areaPath || '-', 22);
|
|
350
|
-
const teamProject = (0, formatHelpers_1.truncateText)(workItem.teamProject || '-', 16);
|
|
351
|
-
result += `| **#${workItem.id}** | ${(0, formatHelpers_1.truncateText)(workItem.title, 36)} | ${typeEmoji} ${workItem.workItemType} | ${stateEmoji} ${workItem.state} | ${assignedTo} | ${createdBy} | ${areaPath} | ${teamProject} |\n`;
|
|
352
|
-
});
|
|
353
|
-
result += `\n---\n\n`;
|
|
354
|
-
// High priority items (one line)
|
|
355
|
-
const highPriorityItems = results.workItems.filter((item) => item.priority && item.priority <= 2);
|
|
356
|
-
if (highPriorityItems.length > 0) {
|
|
357
|
-
const highPriorityIds = highPriorityItems.map((i) => `#${i.id}`).join(', ');
|
|
358
|
-
result += `**High Priority:** ${highPriorityIds} (${highPriorityItems.length} items)\n`;
|
|
359
|
-
}
|
|
360
|
-
// Recently updated (one line, top 3)
|
|
361
|
-
const recentItems = results.workItems
|
|
362
|
-
.sort((a, b) => new Date(b.changedDate).getTime() - new Date(a.changedDate).getTime())
|
|
363
|
-
.slice(0, 3);
|
|
364
|
-
const recentList = recentItems.map((i) => `#${i.id} (${(0, formatHelpers_1.formatRelativeDate)(i.changedDate)})`).join(', ');
|
|
365
|
-
result += `**Recently Updated:** ${recentList}\n\n`;
|
|
366
|
-
result += `---\n`;
|
|
367
|
-
result += `Contains-based search is expensive in Azure DevOps and should be treated as a fallback, not the default discovery path.\n`;
|
|
368
|
-
if (results.wiql) {
|
|
369
|
-
result += `\n**Generated WIQL**\n\n\`\`\`sql\n${results.wiql}\n\`\`\`\n`;
|
|
370
|
-
}
|
|
371
|
-
result += `\nUse \`listWorkItems\` with focused WIQL for repeatable queries, and use \`getWorkItemsBatch\` when you want to fetch a controlled field set for specific IDs.\n`;
|
|
372
|
-
// Prepare structured content
|
|
373
|
-
const structuredData = {
|
|
374
|
-
searchQuery: results.searchQuery,
|
|
375
|
-
totalResults: results.totalResults,
|
|
376
|
-
returnedResults: results.returnedResults,
|
|
377
|
-
recentDaysApplied: results.recentDaysApplied,
|
|
378
|
-
advisory: results.advisory,
|
|
379
|
-
wiql: results.wiql,
|
|
380
|
-
throttleInfo: results.throttleInfo,
|
|
381
|
-
workItems: results.workItems.map((item) => ({
|
|
382
|
-
id: item.id,
|
|
383
|
-
teamProject: item.teamProject,
|
|
384
|
-
type: item.workItemType,
|
|
385
|
-
title: item.title,
|
|
386
|
-
state: item.state,
|
|
387
|
-
priority: item.priority,
|
|
388
|
-
assignedTo: item.assignedTo?.displayName,
|
|
389
|
-
createdBy: item.createdBy?.displayName,
|
|
390
|
-
areaPath: item.areaPath,
|
|
391
|
-
iterationPath: item.iterationPath,
|
|
392
|
-
effort: {
|
|
393
|
-
original: item.originalEstimate,
|
|
394
|
-
completed: item.completedWork,
|
|
395
|
-
remaining: item.remainingWork
|
|
396
|
-
},
|
|
397
|
-
changedDate: item.changedDate
|
|
398
|
-
})),
|
|
399
|
-
summary: {
|
|
400
|
-
byType: typeSummary,
|
|
401
|
-
byStatus: stateSummary,
|
|
402
|
-
effort: totalEffort
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
return (0, Common_1.formatMcpResponse)(structuredData, result, false, true);
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Get recently updated work items
|
|
409
|
-
*/
|
|
410
|
-
async getRecentlyUpdatedWorkItems(params) {
|
|
411
|
-
try {
|
|
412
|
-
const results = await this.workItemService.getRecentWorkItems(params);
|
|
413
|
-
const items = results.workItems || [];
|
|
414
|
-
if (items.length === 0) {
|
|
415
|
-
return (0, Common_1.formatMcpResponse)(results, `## Recently Updated Work Items\n\nNo recently updated work items found.\n\nTry increasing the time range or check project permissions.`);
|
|
416
|
-
}
|
|
417
|
-
let md = `## Recently Updated Work Items\n\n**${items.length} item${items.length !== 1 ? 's' : ''}**\n\n`;
|
|
418
|
-
if (results.recentDaysApplied) {
|
|
419
|
-
md += `Scoped to the last **${results.recentDaysApplied} day${results.recentDaysApplied === 1 ? '' : 's'}**.\n\n`;
|
|
420
|
-
}
|
|
421
|
-
md += this.buildSummaryTable(items, true);
|
|
422
|
-
md += `\n\nUse \`getWorkItemsBatch\` to fetch only the fields you need for selected IDs.`;
|
|
423
|
-
return (0, Common_1.formatMcpResponse)(results, md, false, true);
|
|
424
|
-
}
|
|
425
|
-
catch (error) {
|
|
426
|
-
console.error('Error in getRecentlyUpdatedWorkItems tool:', error);
|
|
427
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Get work items assigned to current user
|
|
432
|
-
*/
|
|
433
|
-
async getMyWorkItems(params) {
|
|
434
|
-
try {
|
|
435
|
-
const results = await this.workItemService.getMyWorkItems(params);
|
|
436
|
-
const items = results.workItems || [];
|
|
437
|
-
if (items.length === 0) {
|
|
438
|
-
return (0, Common_1.formatMcpResponse)(results, `## My Work Items\n\nNo work items assigned to you.\n\nUse \`searchWorkItems\` or \`listWorkItems\` to find items across the project.`);
|
|
439
|
-
}
|
|
440
|
-
let md = `## My Work Items\n\n**${items.length} item${items.length !== 1 ? 's' : ''}** assigned to you\n\n`;
|
|
441
|
-
if (results.recentDaysApplied) {
|
|
442
|
-
md += `Scoped to the last **${results.recentDaysApplied} day${results.recentDaysApplied === 1 ? '' : 's'}** by default.\n\n`;
|
|
443
|
-
}
|
|
444
|
-
md += this.buildSummaryTable(items, true);
|
|
445
|
-
md += `\n\nUse \`getWorkItemsBatch\` to fetch details for selected IDs.`;
|
|
446
|
-
return (0, Common_1.formatMcpResponse)(results, md, false, true);
|
|
447
|
-
}
|
|
448
|
-
catch (error) {
|
|
449
|
-
console.error('Error in getMyWorkItems tool:', error);
|
|
450
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Create a work item
|
|
455
|
-
*/
|
|
456
|
-
async createWorkItem(params) {
|
|
457
|
-
try {
|
|
458
|
-
const workItem = await this.workItemService.createWorkItem(params);
|
|
459
|
-
const typeEmoji = (0, formatHelpers_1.getWorkItemTypeEmoji)(params.workItemType);
|
|
460
|
-
let md = `## Work Item Created\n\n`;
|
|
461
|
-
md += `**#${workItem.id}** ${typeEmoji} ${params.workItemType}`;
|
|
462
|
-
if (workItem.fields?.['System.State'])
|
|
463
|
-
md += ` | ${workItem.fields['System.State']}`;
|
|
464
|
-
if (params.assignedTo)
|
|
465
|
-
md += ` | ${params.assignedTo}`;
|
|
466
|
-
md += `\n`;
|
|
467
|
-
md += `**Title:** ${params.title}\n`;
|
|
468
|
-
if (params.iterationPath)
|
|
469
|
-
md += `**Sprint:** ${params.iterationPath}\n`;
|
|
470
|
-
if (params.areaPath)
|
|
471
|
-
md += `**Area:** ${params.areaPath}\n`;
|
|
472
|
-
return (0, Common_1.formatMcpResponse)(workItem, md, false, true);
|
|
473
|
-
}
|
|
474
|
-
catch (error) {
|
|
475
|
-
console.error('Error in createWorkItem tool:', error);
|
|
476
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Update a work item
|
|
481
|
-
*/
|
|
482
|
-
async updateWorkItem(params) {
|
|
483
|
-
try {
|
|
484
|
-
const workItem = await this.workItemService.updateWorkItem(params);
|
|
485
|
-
let md = `## Work Item Updated\n\n**#${params.id}** updated\n\n`;
|
|
486
|
-
const fields = params.fields || {};
|
|
487
|
-
const changedKeys = Object.keys(fields);
|
|
488
|
-
if (changedKeys.length > 0) {
|
|
489
|
-
md += `**Changed fields:** ${changedKeys.join(', ')}\n`;
|
|
490
|
-
}
|
|
491
|
-
return (0, Common_1.formatMcpResponse)(workItem, md, false, true);
|
|
492
|
-
}
|
|
493
|
-
catch (error) {
|
|
494
|
-
console.error('Error in updateWorkItem tool:', error);
|
|
495
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Get comments on a work item
|
|
500
|
-
*/
|
|
501
|
-
async getWorkItemComments(params) {
|
|
502
|
-
try {
|
|
503
|
-
const commentList = await this.workItemService.getWorkItemComments(params);
|
|
504
|
-
if (!commentList || !Array.isArray(commentList.comments)) {
|
|
505
|
-
throw new Error(`Azure DevOps API returned unexpected response for work item ${params.id} comments`);
|
|
506
|
-
}
|
|
507
|
-
const comments = commentList.comments;
|
|
508
|
-
if (comments.length === 0) {
|
|
509
|
-
return (0, Common_1.formatMcpResponse)(commentList, `## Work Item #${params.id} - Comments\n\nNo comments found.`);
|
|
510
|
-
}
|
|
511
|
-
let md = `## Work Item #${params.id} - Comments\n\n`;
|
|
512
|
-
md += `**${comments.length} comment${comments.length !== 1 ? 's' : ''}**\n\n`;
|
|
513
|
-
for (const comment of comments) {
|
|
514
|
-
const author = comment.createdBy?.displayName || 'Unknown';
|
|
515
|
-
const date = comment.createdDate ? (0, formatHelpers_1.formatFullDate)(comment.createdDate) : 'Unknown date';
|
|
516
|
-
const relDate = comment.createdDate ? (0, formatHelpers_1.formatRelativeDate)(comment.createdDate) : '';
|
|
517
|
-
md += `### Comment #${comment.id}\n`;
|
|
518
|
-
md += `**${author}** | ${date} (${relDate})\n\n`;
|
|
519
|
-
// Prefer raw text (markdown) over renderedText (HTML)
|
|
520
|
-
md += `${comment.text || (0, formatHelpers_1.stripHtml)(comment.renderedText || '') || '(empty)'}\n\n`;
|
|
521
|
-
md += `---\n\n`;
|
|
522
|
-
}
|
|
523
|
-
const structuredData = {
|
|
524
|
-
workItemId: params.id,
|
|
525
|
-
totalCount: commentList?.totalCount ?? comments.length,
|
|
526
|
-
count: comments.length,
|
|
527
|
-
comments: comments.map((c) => ({
|
|
528
|
-
id: c.id,
|
|
529
|
-
text: c.text,
|
|
530
|
-
renderedText: c.renderedText,
|
|
531
|
-
createdBy: c.createdBy ? {
|
|
532
|
-
displayName: c.createdBy.displayName,
|
|
533
|
-
uniqueName: c.createdBy.uniqueName,
|
|
534
|
-
} : null,
|
|
535
|
-
createdDate: c.createdDate,
|
|
536
|
-
modifiedBy: c.modifiedBy ? {
|
|
537
|
-
displayName: c.modifiedBy.displayName,
|
|
538
|
-
uniqueName: c.modifiedBy.uniqueName,
|
|
539
|
-
} : null,
|
|
540
|
-
modifiedDate: c.modifiedDate,
|
|
541
|
-
format: c.format,
|
|
542
|
-
})),
|
|
543
|
-
};
|
|
544
|
-
return (0, Common_1.formatMcpResponse)(structuredData, md, false, true);
|
|
545
|
-
}
|
|
546
|
-
catch (error) {
|
|
547
|
-
console.error('Error in getWorkItemComments tool:', error);
|
|
548
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Add a comment to a work item
|
|
553
|
-
*/
|
|
554
|
-
async addWorkItemComment(params) {
|
|
555
|
-
try {
|
|
556
|
-
const comment = await this.workItemService.addWorkItemComment(params);
|
|
557
|
-
const formatUsed = params.format || 'markdown';
|
|
558
|
-
const md = `## Comment Added\n\n**Work Item:** #${params.id} | **Format:** ${formatUsed}\n\n> ${(0, formatHelpers_1.truncateText)(params.text, 100)}`;
|
|
559
|
-
return (0, Common_1.formatMcpResponse)(comment, md, false, true);
|
|
560
|
-
}
|
|
561
|
-
catch (error) {
|
|
562
|
-
console.error('Error in addWorkItemComment tool:', error);
|
|
563
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Manage (add or update) a work item comment
|
|
568
|
-
*/
|
|
569
|
-
async manageWorkItemComment(params) {
|
|
570
|
-
try {
|
|
571
|
-
const formatUsed = params.format || 'markdown';
|
|
572
|
-
if (params.action === 'update') {
|
|
573
|
-
if (!params.commentId) {
|
|
574
|
-
throw new Error('commentId is required when action is "update"');
|
|
575
|
-
}
|
|
576
|
-
const comment = await this.workItemService.updateWorkItemComment({
|
|
577
|
-
id: params.id,
|
|
578
|
-
commentId: params.commentId,
|
|
579
|
-
text: params.text,
|
|
580
|
-
format: params.format,
|
|
581
|
-
});
|
|
582
|
-
const md = `## Comment Updated\n\n**Work Item:** #${params.id} | **Comment:** #${params.commentId} | **Format:** ${formatUsed}\n\n> ${(0, formatHelpers_1.truncateText)(params.text, 100)}`;
|
|
583
|
-
return (0, Common_1.formatMcpResponse)(comment, md, false, true);
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
const comment = await this.workItemService.addWorkItemComment({
|
|
587
|
-
id: params.id,
|
|
588
|
-
text: params.text,
|
|
589
|
-
format: params.format,
|
|
590
|
-
});
|
|
591
|
-
const md = `## Comment Added\n\n**Work Item:** #${params.id} | **Format:** ${formatUsed}\n\n> ${(0, formatHelpers_1.truncateText)(params.text, 100)}`;
|
|
592
|
-
return (0, Common_1.formatMcpResponse)(comment, md, false, true);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
catch (error) {
|
|
596
|
-
console.error('Error in manageWorkItemComment tool:', error);
|
|
597
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Update work item state
|
|
602
|
-
*/
|
|
603
|
-
async updateWorkItemState(params) {
|
|
604
|
-
try {
|
|
605
|
-
const workItem = await this.workItemService.updateWorkItemState(params);
|
|
606
|
-
const stateEmoji = (0, formatHelpers_1.getStateEmoji)(params.state);
|
|
607
|
-
let md = `## State Updated\n\n**#${params.id}** -> ${stateEmoji} ${params.state}`;
|
|
608
|
-
if (params.comment)
|
|
609
|
-
md += `\n\n> ${(0, formatHelpers_1.truncateText)(params.comment, 100)}`;
|
|
610
|
-
return (0, Common_1.formatMcpResponse)(workItem, md, false, true);
|
|
611
|
-
}
|
|
612
|
-
catch (error) {
|
|
613
|
-
console.error('Error in updateWorkItemState tool:', error);
|
|
614
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Assign work item to a user
|
|
619
|
-
*/
|
|
620
|
-
async assignWorkItem(params) {
|
|
621
|
-
try {
|
|
622
|
-
const workItem = await this.workItemService.assignWorkItem(params);
|
|
623
|
-
const md = `## Work Item Assigned\n\n**#${params.id}** -> ${params.assignedTo}`;
|
|
624
|
-
return (0, Common_1.formatMcpResponse)(workItem, md, false, true);
|
|
625
|
-
}
|
|
626
|
-
catch (error) {
|
|
627
|
-
console.error('Error in assignWorkItem tool:', error);
|
|
628
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Create a link between a work item and another work item or artifact
|
|
633
|
-
*/
|
|
634
|
-
async createLink(params) {
|
|
635
|
-
try {
|
|
636
|
-
const parsed = parseTargetId(params.targetId);
|
|
637
|
-
// Validate repository is provided for types that need it
|
|
638
|
-
if (['pr', 'branch', 'commit'].includes(parsed.type) && !params.repository) {
|
|
639
|
-
throw new Error(`The 'repository' parameter is required for ${parsed.displayName} links. ` +
|
|
640
|
-
`Please provide the repository name or ID.`);
|
|
641
|
-
}
|
|
642
|
-
let serviceParams;
|
|
643
|
-
if (parsed.type === 'workitem') {
|
|
644
|
-
// Work item link (existing behavior)
|
|
645
|
-
const targetWiId = parseInt(parsed.id, 10);
|
|
646
|
-
if (isNaN(targetWiId)) {
|
|
647
|
-
throw new Error(`Invalid work item ID: '${parsed.id}'. Must be a number.`);
|
|
648
|
-
}
|
|
649
|
-
serviceParams = {
|
|
650
|
-
sourceId: params.sourceId,
|
|
651
|
-
linkType: params.linkType,
|
|
652
|
-
comment: params.comment,
|
|
653
|
-
targetWorkItemId: targetWiId,
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
// Artifact link - build vstfs URI
|
|
658
|
-
const projectId = await this.resolveProjectId();
|
|
659
|
-
let artifactUri;
|
|
660
|
-
let repoId;
|
|
661
|
-
if (params.repository) {
|
|
662
|
-
repoId = await this.gitService.resolveRepositoryId(params.repository);
|
|
663
|
-
}
|
|
664
|
-
// Azure DevOps artifact URIs use %2F (URL-encoded /) between composite ID segments
|
|
665
|
-
// e.g. vstfs:///Git/PullRequestId/{projectId}%2F{repoId}%2F{prId}
|
|
666
|
-
switch (parsed.type) {
|
|
667
|
-
case 'pr':
|
|
668
|
-
artifactUri = `vstfs:///Git/PullRequestId/${projectId}%2F${repoId}%2F${parsed.id}`;
|
|
669
|
-
break;
|
|
670
|
-
case 'build':
|
|
671
|
-
artifactUri = `vstfs:///Build/Build/${parsed.id}`;
|
|
672
|
-
break;
|
|
673
|
-
case 'branch': {
|
|
674
|
-
const branchRef = parsed.id.startsWith('GB') ? parsed.id : `GB${parsed.id}`;
|
|
675
|
-
artifactUri = `vstfs:///Git/Ref/${projectId}%2F${repoId}%2F${branchRef}`;
|
|
676
|
-
break;
|
|
677
|
-
}
|
|
678
|
-
case 'commit':
|
|
679
|
-
artifactUri = `vstfs:///Git/Commit/${projectId}%2F${repoId}%2F${parsed.id}`;
|
|
680
|
-
break;
|
|
681
|
-
default:
|
|
682
|
-
throw new Error(`Unsupported artifact type: ${parsed.type}`);
|
|
683
|
-
}
|
|
684
|
-
serviceParams = {
|
|
685
|
-
sourceId: params.sourceId,
|
|
686
|
-
linkType: params.linkType,
|
|
687
|
-
comment: params.comment,
|
|
688
|
-
artifactUri,
|
|
689
|
-
artifactName: parsed.displayName,
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
const workItem = await this.workItemService.createLink(serviceParams);
|
|
693
|
-
// Build readable response
|
|
694
|
-
const targetLabel = parsed.type === 'workitem'
|
|
695
|
-
? `**WI#${parsed.id}**`
|
|
696
|
-
: `**${parsed.type.toUpperCase()}#${parsed.id}**`;
|
|
697
|
-
const md = `## Link Created\n\n**WI#${params.sourceId}** <-> ${targetLabel} (${parsed.displayName})`;
|
|
698
|
-
const structuredData = {
|
|
699
|
-
sourceId: params.sourceId,
|
|
700
|
-
targetId: params.targetId,
|
|
701
|
-
targetType: parsed.type,
|
|
702
|
-
targetDisplayName: parsed.displayName,
|
|
703
|
-
linkType: params.linkType,
|
|
704
|
-
};
|
|
705
|
-
return (0, Common_1.formatMcpResponse)(structuredData, md, false, true);
|
|
706
|
-
}
|
|
707
|
-
catch (error) {
|
|
708
|
-
console.error('Error in createLink tool:', error);
|
|
709
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* Bulk create or update work items
|
|
714
|
-
*/
|
|
715
|
-
async bulkCreateWorkItems(params) {
|
|
716
|
-
try {
|
|
717
|
-
const results = await this.workItemService.bulkUpdateWorkItems(params);
|
|
718
|
-
const created = results.created || [];
|
|
719
|
-
const updated = results.updated || [];
|
|
720
|
-
const count = results.count || (created.length + updated.length);
|
|
721
|
-
let md = `## Bulk Operation Complete\n\n**${count} work items processed**`;
|
|
722
|
-
if (created.length > 0)
|
|
723
|
-
md += ` | ${created.length} created`;
|
|
724
|
-
if (updated.length > 0)
|
|
725
|
-
md += ` | ${updated.length} updated`;
|
|
726
|
-
md += '\n';
|
|
727
|
-
if (created.length > 0) {
|
|
728
|
-
const ids = created.map((wi) => `#${wi.id}`).join(', ');
|
|
729
|
-
md += `\n**Created:** ${ids}`;
|
|
730
|
-
}
|
|
731
|
-
if (updated.length > 0) {
|
|
732
|
-
const ids = updated.map((wi) => `#${wi.id}`).join(', ');
|
|
733
|
-
md += `\n**Updated:** ${ids}`;
|
|
734
|
-
}
|
|
735
|
-
return (0, Common_1.formatMcpResponse)(results, md, false, true);
|
|
736
|
-
}
|
|
737
|
-
catch (error) {
|
|
738
|
-
console.error('Error in bulkCreateWorkItems tool:', error);
|
|
739
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
// New Work Item Enhancement Tools
|
|
743
|
-
/**
|
|
744
|
-
* Get multiple work items by IDs in a single call
|
|
745
|
-
*/
|
|
746
|
-
async getWorkItemsBatch(params) {
|
|
747
|
-
try {
|
|
748
|
-
const workItems = await this.workItemService.getWorkItemsBatch(params);
|
|
749
|
-
if (workItems.length === 0) {
|
|
750
|
-
return (0, Common_1.formatMcpResponse)(workItems, `## Work Items Batch\n\nNo work items found for the given IDs.`);
|
|
751
|
-
}
|
|
752
|
-
let md = `## Work Items Batch\n\n**${workItems.length} work item${workItems.length !== 1 ? 's' : ''}** retrieved\n\n`;
|
|
753
|
-
const rows = workItems.map((wi) => {
|
|
754
|
-
const fields = wi.fields || {};
|
|
755
|
-
return [
|
|
756
|
-
`#${wi.id}`,
|
|
757
|
-
(0, formatHelpers_1.getWorkItemTypeEmoji)(fields['System.WorkItemType'] || '') + ' ' + (fields['System.WorkItemType'] || '-'),
|
|
758
|
-
(0, formatHelpers_1.truncateText)(fields['System.Title'] || '-', 50),
|
|
759
|
-
(0, formatHelpers_1.getStateEmoji)(fields['System.State'] || '') + ' ' + (fields['System.State'] || '-'),
|
|
760
|
-
fields['System.AssignedTo']?.displayName || '-',
|
|
761
|
-
];
|
|
762
|
-
});
|
|
763
|
-
md += (0, formatHelpers_1.markdownTable)(['ID', 'Type', 'Title', 'State', 'Assigned To'], rows);
|
|
764
|
-
return (0, Common_1.formatMcpResponse)(workItems, md, false, true);
|
|
765
|
-
}
|
|
766
|
-
catch (error) {
|
|
767
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* Get revision history for a work item
|
|
772
|
-
*/
|
|
773
|
-
async getWorkItemRevisions(params) {
|
|
774
|
-
try {
|
|
775
|
-
const revisions = await this.workItemService.getWorkItemRevisions(params);
|
|
776
|
-
if (revisions.length === 0) {
|
|
777
|
-
return (0, Common_1.formatMcpResponse)(revisions, `## Work Item #${params.id} - Revisions\n\nNo revisions found.`);
|
|
778
|
-
}
|
|
779
|
-
let md = `## Work Item #${params.id} - Revision History\n\n`;
|
|
780
|
-
md += `**${revisions.length} revision${revisions.length !== 1 ? 's' : ''}**\n\n`;
|
|
781
|
-
const rows = revisions.map((rev, index) => {
|
|
782
|
-
const fields = rev.fields || {};
|
|
783
|
-
return [
|
|
784
|
-
`${rev.rev || index + 1}`,
|
|
785
|
-
(0, formatHelpers_1.truncateText)(fields['System.Title'] || '-', 40),
|
|
786
|
-
fields['System.State'] || '-',
|
|
787
|
-
fields['System.ChangedBy']?.displayName || fields['System.ChangedBy'] || '-',
|
|
788
|
-
fields['System.ChangedDate'] ? (0, formatHelpers_1.formatRelativeDate)(fields['System.ChangedDate']) : '-',
|
|
789
|
-
];
|
|
790
|
-
});
|
|
791
|
-
md += (0, formatHelpers_1.markdownTable)(['Rev', 'Title', 'State', 'Changed By', 'Changed'], rows);
|
|
792
|
-
return (0, Common_1.formatMcpResponse)(revisions, md, false, true);
|
|
793
|
-
}
|
|
794
|
-
catch (error) {
|
|
795
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* Execute a saved WIQL query by query ID
|
|
800
|
-
*/
|
|
801
|
-
async getQueryResults(params) {
|
|
802
|
-
try {
|
|
803
|
-
const result = await this.workItemService.getQueryResults(params);
|
|
804
|
-
const workItems = result.workItems || [];
|
|
805
|
-
if (workItems.length === 0) {
|
|
806
|
-
return (0, Common_1.formatMcpResponse)(result, `## Query Results\n\nNo work items returned by query \`${params.queryId}\`.`);
|
|
807
|
-
}
|
|
808
|
-
let md = `## Query Results\n\n`;
|
|
809
|
-
md += `**${workItems.length} work item${workItems.length !== 1 ? 's' : ''}** returned\n\n`;
|
|
810
|
-
if (result.queryType) {
|
|
811
|
-
md += `**Query Type:** ${result.queryType}\n\n`;
|
|
812
|
-
}
|
|
813
|
-
if (result.columns?.length) {
|
|
814
|
-
md += `**Saved Query Columns:** ${result.columns.join(', ')}\n\n`;
|
|
815
|
-
}
|
|
816
|
-
md += this.buildSummaryTable(workItems);
|
|
817
|
-
md += `\n\nUse \`getWorkItemsBatch\` with the IDs above and an explicit field list to hydrate only what you need.`;
|
|
818
|
-
return (0, Common_1.formatMcpResponse)(result, md, false, true);
|
|
819
|
-
}
|
|
820
|
-
catch (error) {
|
|
821
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
/**
|
|
825
|
-
* Create a child work item linked to a parent
|
|
826
|
-
*/
|
|
827
|
-
async addChildWorkItem(params) {
|
|
828
|
-
try {
|
|
829
|
-
const workItem = await this.workItemService.addChildWorkItem(params);
|
|
830
|
-
let md = `## Child Work Item Created\n\n`;
|
|
831
|
-
md += `| Property | Value |\n|---|---|\n`;
|
|
832
|
-
md += `| **ID** | #${workItem.id} |\n`;
|
|
833
|
-
md += `| **Parent ID** | #${params.parentId} |\n`;
|
|
834
|
-
md += `| **Type** | ${params.workItemType} |\n`;
|
|
835
|
-
md += `| **Title** | ${params.title} |\n`;
|
|
836
|
-
if (params.assignedTo)
|
|
837
|
-
md += `| **Assigned To** | ${params.assignedTo} |\n`;
|
|
838
|
-
return (0, Common_1.formatMcpResponse)(workItem, md, false, true);
|
|
839
|
-
}
|
|
840
|
-
catch (error) {
|
|
841
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* Remove a relation (link) from a work item by relation index
|
|
846
|
-
*/
|
|
847
|
-
async unlinkWorkItem(params) {
|
|
848
|
-
try {
|
|
849
|
-
const result = await this.workItemService.unlinkWorkItem(params);
|
|
850
|
-
let md = `## Work Item #${params.id} - Link Removed\n\n`;
|
|
851
|
-
md += `Relation at index **${params.relationIndex}** has been removed.\n`;
|
|
852
|
-
md += `\nUse \`getWorkItemById\` to see remaining relations.`;
|
|
853
|
-
return (0, Common_1.formatMcpResponse)(result, md, false, true);
|
|
854
|
-
}
|
|
855
|
-
catch (error) {
|
|
856
|
-
return (0, Common_1.formatErrorResponse)(error);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
exports.WorkItemTools = WorkItemTools;
|
|
861
|
-
exports.WorkItemToolMethods = (0, getClassMethods_1.default)(WorkItemTools.prototype);
|
|
862
|
-
//# sourceMappingURL=WorkItemTools.js.map
|