@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.
Files changed (114) hide show
  1. package/dist/Interfaces/Common.js +10 -1
  2. package/dist/Interfaces/Common.js.map +1 -1
  3. package/dist/Services/AzureDevOpsService.js +234 -32
  4. package/dist/Services/AzureDevOpsService.js.map +1 -1
  5. package/dist/Services/BoardsSprintsService.js +105 -13
  6. package/dist/Services/BoardsSprintsService.js.map +1 -1
  7. package/dist/Services/BuildService.js +151 -24
  8. package/dist/Services/BuildService.js.map +1 -1
  9. package/dist/Services/GitService.js +26 -3
  10. package/dist/Services/GitService.js.map +1 -1
  11. package/dist/Services/ProjectService.js +47 -6
  12. package/dist/Services/ProjectService.js.map +1 -1
  13. package/dist/Services/WorkItemService.js +183 -142
  14. package/dist/Services/WorkItemService.js.map +1 -1
  15. package/dist/Tools/BoardsSprintsTools.js +2 -8
  16. package/dist/Tools/BoardsSprintsTools.js.map +1 -1
  17. package/dist/Tools/BuildTools.js +5 -8
  18. package/dist/Tools/BuildTools.js.map +1 -1
  19. package/dist/Tools/WorkItemTools.js +111 -74
  20. package/dist/Tools/WorkItemTools.js.map +1 -1
  21. package/dist/index.js +22 -12
  22. package/dist/index.js.map +1 -1
  23. package/dist/utils/formatHelpers.js +15 -0
  24. package/dist/utils/formatHelpers.js.map +1 -1
  25. package/package.json +2 -2
  26. package/dist/Services/BuildService.project.test.js +0 -91
  27. package/dist/Services/BuildService.project.test.js.map +0 -1
  28. package/dist/Services/GitService.project.test.js +0 -407
  29. package/dist/Services/GitService.project.test.js.map +0 -1
  30. package/dist/package.json +0 -59
  31. package/dist/src/Interfaces/AIAssisted.js +0 -3
  32. package/dist/src/Interfaces/AIAssisted.js.map +0 -1
  33. package/dist/src/Interfaces/ArtifactManagement.js +0 -3
  34. package/dist/src/Interfaces/ArtifactManagement.js.map +0 -1
  35. package/dist/src/Interfaces/AzureDevOps.js +0 -3
  36. package/dist/src/Interfaces/AzureDevOps.js.map +0 -1
  37. package/dist/src/Interfaces/BoardsAndSprints.js +0 -3
  38. package/dist/src/Interfaces/BoardsAndSprints.js.map +0 -1
  39. package/dist/src/Interfaces/CodeAndRepositories.js +0 -3
  40. package/dist/src/Interfaces/CodeAndRepositories.js.map +0 -1
  41. package/dist/src/Interfaces/Common.js +0 -134
  42. package/dist/src/Interfaces/Common.js.map +0 -1
  43. package/dist/src/Interfaces/CostResourceManagement.js +0 -3
  44. package/dist/src/Interfaces/CostResourceManagement.js.map +0 -1
  45. package/dist/src/Interfaces/DevSecOps.js +0 -3
  46. package/dist/src/Interfaces/DevSecOps.js.map +0 -1
  47. package/dist/src/Interfaces/ExternalIntegrations.js +0 -3
  48. package/dist/src/Interfaces/ExternalIntegrations.js.map +0 -1
  49. package/dist/src/Interfaces/HybridCrossPlatform.js +0 -3
  50. package/dist/src/Interfaces/HybridCrossPlatform.js.map +0 -1
  51. package/dist/src/Interfaces/Pipelines.js +0 -3
  52. package/dist/src/Interfaces/Pipelines.js.map +0 -1
  53. package/dist/src/Interfaces/ProjectManagement.js +0 -3
  54. package/dist/src/Interfaces/ProjectManagement.js.map +0 -1
  55. package/dist/src/Interfaces/TestingCapabilities.js +0 -3
  56. package/dist/src/Interfaces/TestingCapabilities.js.map +0 -1
  57. package/dist/src/Interfaces/Wiki.js +0 -3
  58. package/dist/src/Interfaces/Wiki.js.map +0 -1
  59. package/dist/src/Interfaces/WorkItems.js +0 -3
  60. package/dist/src/Interfaces/WorkItems.js.map +0 -1
  61. package/dist/src/Services/AIAssistedDevelopmentService.js +0 -195
  62. package/dist/src/Services/AIAssistedDevelopmentService.js.map +0 -1
  63. package/dist/src/Services/ArtifactManagementService.js +0 -346
  64. package/dist/src/Services/ArtifactManagementService.js.map +0 -1
  65. package/dist/src/Services/AzureDevOpsService.js +0 -385
  66. package/dist/src/Services/AzureDevOpsService.js.map +0 -1
  67. package/dist/src/Services/BoardsSprintsService.js +0 -339
  68. package/dist/src/Services/BoardsSprintsService.js.map +0 -1
  69. package/dist/src/Services/BuildService.js +0 -405
  70. package/dist/src/Services/BuildService.js.map +0 -1
  71. package/dist/src/Services/DevSecOpsService.js +0 -307
  72. package/dist/src/Services/DevSecOpsService.js.map +0 -1
  73. package/dist/src/Services/EntraAuthHandler.js +0 -337
  74. package/dist/src/Services/EntraAuthHandler.js.map +0 -1
  75. package/dist/src/Services/GitService.js +0 -1595
  76. package/dist/src/Services/GitService.js.map +0 -1
  77. package/dist/src/Services/ProjectService.js +0 -257
  78. package/dist/src/Services/ProjectService.js.map +0 -1
  79. package/dist/src/Services/TestingCapabilitiesService.js +0 -149
  80. package/dist/src/Services/TestingCapabilitiesService.js.map +0 -1
  81. package/dist/src/Services/WikiService.js +0 -90
  82. package/dist/src/Services/WikiService.js.map +0 -1
  83. package/dist/src/Services/WorkItemService.js +0 -885
  84. package/dist/src/Services/WorkItemService.js.map +0 -1
  85. package/dist/src/Tools/AIAssistedDevelopmentTools.js +0 -137
  86. package/dist/src/Tools/AIAssistedDevelopmentTools.js.map +0 -1
  87. package/dist/src/Tools/ArtifactManagementTools.js +0 -140
  88. package/dist/src/Tools/ArtifactManagementTools.js.map +0 -1
  89. package/dist/src/Tools/BoardsSprintsTools.js +0 -338
  90. package/dist/src/Tools/BoardsSprintsTools.js.map +0 -1
  91. package/dist/src/Tools/BuildTools.js +0 -468
  92. package/dist/src/Tools/BuildTools.js.map +0 -1
  93. package/dist/src/Tools/DevSecOpsTools.js +0 -147
  94. package/dist/src/Tools/DevSecOpsTools.js.map +0 -1
  95. package/dist/src/Tools/GitTools.js +0 -1475
  96. package/dist/src/Tools/GitTools.js.map +0 -1
  97. package/dist/src/Tools/ProjectTools.js +0 -360
  98. package/dist/src/Tools/ProjectTools.js.map +0 -1
  99. package/dist/src/Tools/TestingCapabilitiesTools.js +0 -157
  100. package/dist/src/Tools/TestingCapabilitiesTools.js.map +0 -1
  101. package/dist/src/Tools/WikiTools.js +0 -137
  102. package/dist/src/Tools/WikiTools.js.map +0 -1
  103. package/dist/src/Tools/WorkItemTools.js +0 -862
  104. package/dist/src/Tools/WorkItemTools.js.map +0 -1
  105. package/dist/src/config.js +0 -176
  106. package/dist/src/config.js.map +0 -1
  107. package/dist/src/index.js +0 -1716
  108. package/dist/src/index.js.map +0 -1
  109. package/dist/src/utils/formatHelpers.js +0 -257
  110. package/dist/src/utils/formatHelpers.js.map +0 -1
  111. package/dist/src/utils/getClassMethods.js +0 -8
  112. package/dist/src/utils/getClassMethods.js.map +0 -1
  113. package/dist/src/utils/repositoryResolver.js +0 -40
  114. 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