@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,1475 +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.GitToolMethods = exports.GitTools = void 0;
7
- const GitService_1 = require("../Services/GitService");
8
- const Common_1 = require("../Interfaces/Common");
9
- const getClassMethods_1 = __importDefault(require("../utils/getClassMethods"));
10
- const formatHelpers_1 = require("../utils/formatHelpers");
11
- class GitTools {
12
- constructor(config) {
13
- this.gitService = new GitService_1.GitService(config);
14
- }
15
- /**
16
- * Generate a work-item reminder for PR create/update responses.
17
- */
18
- async getWorkItemReminder(repository, pullRequestId) {
19
- try {
20
- const workItems = await this.gitService.getPullRequestWorkItemRefs(repository, pullRequestId);
21
- if (workItems.length === 0) {
22
- return `\n\n> **Reminder:** No work item is linked to this PR. Consider linking a Bug/User Story/Task using \`linkWorkItemToPullRequest\` so the work is tracked and discoverable.\n`;
23
- }
24
- const ids = workItems.map(w => `#${w.id}`).join(', ');
25
- return `\n\n> **Reminder:** This PR has linked work item(s): ${ids}. Make sure the work item description/comments capture the learnings and context of this change (what was done and why) so it's understandable without reading the code.\n`;
26
- }
27
- catch {
28
- return '';
29
- }
30
- }
31
- /**
32
- * List all repositories
33
- */
34
- async listRepositories(params) {
35
- try {
36
- const repositories = await this.gitService.listRepositories(params);
37
- const formattedTable = this.formatRepositoriesTable(repositories);
38
- return (0, Common_1.formatMcpResponse)(repositories, formattedTable);
39
- }
40
- catch (error) {
41
- console.error('Error in listRepositories tool:', error);
42
- return (0, Common_1.formatErrorResponse)(error);
43
- }
44
- }
45
- /**
46
- * Formats repositories data into a readable table format
47
- */
48
- formatRepositoriesTable(repositories) {
49
- if (!repositories || repositories.length === 0) {
50
- return "No repositories found.";
51
- }
52
- // Table header
53
- let table = "## Repositories\n\n";
54
- table += "| Repository Name | Repository Id | Repository URL |\n";
55
- table += "|-----------------|---------------|----------------|\n";
56
- // Table rows
57
- repositories.forEach(repo => {
58
- const name = repo.name || 'N/A';
59
- const id = repo.id || 'N/A';
60
- const url = repo.webUrl || repo.remoteUrl || 'N/A';
61
- table += `| ${name} | ${id} | ${url} |\n`;
62
- });
63
- table += `\n**Total repositories:** ${repositories.length}`;
64
- return table;
65
- }
66
- /**
67
- * Get repository details
68
- */
69
- async getRepository(params) {
70
- try {
71
- const repository = await this.gitService.getRepository(params);
72
- let md = `## Repository: ${repository.name || 'Unknown'}\n\n`;
73
- if (repository.id)
74
- md += `**ID:** ${repository.id}\n`;
75
- md += `**Default Branch:** ${repository.defaultBranch?.replace('refs/heads/', '') || 'N/A'}\n`;
76
- if (repository.size)
77
- md += `**Size:** ${(repository.size / 1024).toFixed(1)} KB\n`;
78
- if (repository.remoteUrl)
79
- md += `**Clone URL:** ${repository.remoteUrl}\n`;
80
- if (repository.webUrl)
81
- md += `**Web URL:** ${repository.webUrl}\n`;
82
- if (repository.project?.name)
83
- md += `**Project:** ${repository.project.name}\n`;
84
- return (0, Common_1.formatMcpResponse)(repository, md, false, true);
85
- }
86
- catch (error) {
87
- console.error('Error in getRepository tool:', error);
88
- return (0, Common_1.formatErrorResponse)(error);
89
- }
90
- }
91
- /**
92
- * Create a repository
93
- */
94
- async createRepository(params) {
95
- try {
96
- const repository = await this.gitService.createRepository(params);
97
- let md = `## ✅ Repository Created\n\n**${repository.name || params.name}**\n`;
98
- if (repository.id)
99
- md += `**ID:** ${repository.id}\n`;
100
- if (repository.remoteUrl)
101
- md += `**Clone URL:** ${repository.remoteUrl}\n`;
102
- if (repository.webUrl)
103
- md += `**Web URL:** ${repository.webUrl}\n`;
104
- return (0, Common_1.formatMcpResponse)(repository, md, false, true);
105
- }
106
- catch (error) {
107
- console.error('Error in createRepository tool:', error);
108
- return (0, Common_1.formatErrorResponse)(error);
109
- }
110
- }
111
- /**
112
- * List branches
113
- */
114
- async listBranches(params) {
115
- try {
116
- const branches = await this.gitService.listBranches(params);
117
- const items = Array.isArray(branches) ? branches : [];
118
- if (items.length === 0) {
119
- return (0, Common_1.formatMcpResponse)(branches, `## Branches\n\nNo branches found in repository \`${params.repository}\`.\n\n💡 The repository may be empty.`);
120
- }
121
- let md = `## Branches\n\n**${items.length} branch${items.length !== 1 ? 'es' : ''}** in \`${params.repository}\`\n\n`;
122
- const rows = items.map((b) => {
123
- const name = b.name?.replace('refs/heads/', '') || 'N/A';
124
- const shortCommit = b.commit?.commitId?.substring(0, 8) || b.objectId?.substring(0, 8) || '-';
125
- const ahead = b.aheadCount != null ? String(b.aheadCount) : '-';
126
- const behind = b.behindCount != null ? String(b.behindCount) : '-';
127
- return [name, `\`${shortCommit}\``, ahead, behind];
128
- });
129
- md += (0, formatHelpers_1.markdownTable)(['Name', 'Commit', 'Ahead', 'Behind'], rows);
130
- return (0, Common_1.formatMcpResponse)(branches, md, false, true);
131
- }
132
- catch (error) {
133
- console.error('Error in listBranches tool:', error);
134
- return (0, Common_1.formatErrorResponse)(error);
135
- }
136
- }
137
- /**
138
- * Search code
139
- */
140
- async searchCode(params) {
141
- try {
142
- const items = await this.gitService.searchCode(params);
143
- const results = Array.isArray(items) ? items : (items?.results || items?.value || []);
144
- if (results.length === 0) {
145
- return (0, Common_1.formatMcpResponse)(items, `## Code Search\n\nNo results found for "${params.searchText || ''}".\n\n💡 Try different keywords or check the repository name.`);
146
- }
147
- let md = `## Code Search Results\n\n**${results.length} result${results.length !== 1 ? 's' : ''}**\n\n`;
148
- const rows = results.map((r) => [
149
- r.path || r.fileName || 'N/A',
150
- r.fileName || r.path?.split('/').pop() || '-',
151
- r.fileExtension || r.path?.split('.').pop() || '-'
152
- ]);
153
- md += (0, formatHelpers_1.markdownTable)(['Path', 'File', 'Extension'], rows);
154
- return (0, Common_1.formatMcpResponse)(items, md, false, true);
155
- }
156
- catch (error) {
157
- console.error('Error in searchCode tool:', error);
158
- return (0, Common_1.formatErrorResponse)(error);
159
- }
160
- }
161
- /**
162
- * Browse repository
163
- */
164
- async browseRepository(params) {
165
- try {
166
- const items = await this.gitService.browseRepository(params);
167
- const entries = Array.isArray(items) ? items : (items?.value || []);
168
- const path = params.path || '/';
169
- if (entries.length === 0) {
170
- return (0, Common_1.formatMcpResponse)(items, `## Browse: \`${path}\`\n\nNo items found at this path.\n\n💡 Check the path or use \`listBranches\` to verify the branch.`);
171
- }
172
- const folders = entries.filter((e) => e.isFolder || e.gitObjectType === 'tree');
173
- const files = entries.filter((e) => !e.isFolder && e.gitObjectType !== 'tree');
174
- let md = `## Browse: \`${path}\`\n\n`;
175
- md += `**${entries.length} items** | ${folders.length} folders, ${files.length} files\n\n`;
176
- md += '```\n';
177
- // Folders first, then files
178
- folders.forEach((f) => {
179
- const segments = f.path?.split('/').filter(Boolean) || [];
180
- const name = segments.length > 0 ? segments[segments.length - 1] : (f.name || '.');
181
- md += `📁 ${name}/\n`;
182
- });
183
- files.forEach((f) => {
184
- const segments = f.path?.split('/').filter(Boolean) || [];
185
- const name = segments.length > 0 ? segments[segments.length - 1] : (f.name || 'N/A');
186
- md += `📄 ${name}\n`;
187
- });
188
- md += '```\n';
189
- return (0, Common_1.formatMcpResponse)(items, md, false, true);
190
- }
191
- catch (error) {
192
- console.error('Error in browseRepository tool:', error);
193
- return (0, Common_1.formatErrorResponse)(error);
194
- }
195
- }
196
- /**
197
- * Get file content
198
- */
199
- async getFileContent(params) {
200
- try {
201
- const file = await this.gitService.getFileContent(params);
202
- const formattedContent = this.formatFileContent(file, params.path, params);
203
- // Extract metadata from service response
204
- const content = file.content || '';
205
- const metadata = file.metadata || {};
206
- const lines = content.split('\n');
207
- const actualLineCount = lines.length;
208
- const sizeInBytes = Buffer.byteLength(content, 'utf8');
209
- // Truncation limits
210
- const maxLines = 200;
211
- const maxChars = 8000;
212
- // Determine truncation for structured content
213
- let truncated = false;
214
- let truncatedDueTo = 'none';
215
- let structuredContent = content;
216
- // Apply line limit
217
- if (actualLineCount > maxLines) {
218
- const truncatedLines = lines.slice(0, maxLines);
219
- structuredContent = truncatedLines.join('\n');
220
- truncated = true;
221
- truncatedDueTo = 'lineLimit';
222
- }
223
- // Apply character limit
224
- if (structuredContent.length > maxChars) {
225
- // Find how many lines fit within char limit
226
- let charCount = 0;
227
- let linesFit = 0;
228
- const linesToCheck = structuredContent.split('\n');
229
- for (let i = 0; i < linesToCheck.length; i++) {
230
- const lineWithNewline = linesToCheck[i] + '\n';
231
- if (charCount + lineWithNewline.length > maxChars) {
232
- break;
233
- }
234
- charCount += lineWithNewline.length;
235
- linesFit++;
236
- }
237
- structuredContent = linesToCheck.slice(0, linesFit).join('\n');
238
- truncated = true;
239
- truncatedDueTo = 'charLimit';
240
- }
241
- // Calculate effective end line for structured content
242
- const structuredLines = structuredContent.split('\n');
243
- const effectiveStartLine = metadata.startLine || 1;
244
- const effectiveEndLine = effectiveStartLine + structuredLines.length - 1;
245
- // Return with enhanced structured content
246
- return (0, Common_1.formatMcpResponse)({
247
- path: params.path,
248
- content: structuredContent,
249
- metadata: {
250
- startLine: effectiveStartLine,
251
- endLine: effectiveEndLine,
252
- totalLines: metadata.totalLines || actualLineCount,
253
- requestedStartLine: params.startLine || 1,
254
- requestedLineCount: params.lineCount || metadata.totalLines || actualLineCount,
255
- actualLineCount: structuredLines.length,
256
- size: sizeInBytes,
257
- encoding: 'utf-8',
258
- truncated: truncated,
259
- truncatedDueTo: truncatedDueTo,
260
- maxLinesPerRequest: maxLines,
261
- maxCharsPerRequest: maxChars,
262
- ...(params.versionDescriptor?.version && { version: params.versionDescriptor.version })
263
- }
264
- }, formattedContent, false, true // Enable structured content
265
- );
266
- }
267
- catch (error) {
268
- console.error('Error in getFileContent tool:', error);
269
- return (0, Common_1.formatErrorResponse)(error);
270
- }
271
- }
272
- /**
273
- * Get commit history
274
- */
275
- async getCommitHistory(params) {
276
- try {
277
- const commits = await this.gitService.getCommitHistory(params);
278
- return this.formatCommitHistoryResponse(commits, params);
279
- }
280
- catch (error) {
281
- console.error('Error in getCommitHistory tool:', error);
282
- return (0, Common_1.formatErrorResponse)(error);
283
- }
284
- }
285
- /**
286
- * Format commit history response with concise, LLM-optimized formatting
287
- */
288
- formatCommitHistoryResponse(commits, params) {
289
- if (!commits || commits.length === 0) {
290
- return (0, Common_1.formatMcpResponse)({ commits: [], totalCommits: 0, repository: params.repository }, `## Commit History\n\n**No commits found** for the specified criteria.\n\n**Repository:** ${params.repository}\n${params.itemPath ? `**File Path:** ${params.itemPath}\n` : ''}**Total commits:** 0`);
291
- }
292
- // Helper function to format author
293
- const formatAuthor = (author) => {
294
- if (!author)
295
- return 'Unknown';
296
- return author.displayName || author.name || author.email || 'Unknown';
297
- };
298
- // Helper function to format commit message
299
- const formatCommitMessage = (message) => {
300
- if (!message)
301
- return { title: 'No commit message', description: '' };
302
- const lines = message.trim().split('\n');
303
- const title = lines[0] || 'No commit message';
304
- const description = lines.slice(1).join('\n').trim();
305
- return { title, description };
306
- };
307
- // Calculate summary statistics upfront
308
- const totalAuthors = new Set(commits.map((c) => formatAuthor(c.author))).size;
309
- // START WITH SUMMARY AT TOP
310
- let result = `## Commit History\n\n`;
311
- result += `**${commits.length} commits** | **${totalAuthors} contributor${totalAuthors > 1 ? 's' : ''}**`;
312
- if (params.itemPath) {
313
- result += ` | **Path:** \`${params.itemPath}\``;
314
- }
315
- result += `\n\n`;
316
- // Optional pagination info
317
- if (params.skip && params.skip > 0) {
318
- result += `⚠️ Skipped ${params.skip} commits\n\n`;
319
- }
320
- result += `---\n\n`;
321
- // Commit list (simplified inline format)
322
- commits.forEach((commit, index) => {
323
- const { title, description } = formatCommitMessage(commit.comment);
324
- const author = formatAuthor(commit.author);
325
- const commitDate = commit.author?.date || commit.committer?.date;
326
- const shortDate = commitDate ? new Date(commitDate).toLocaleDateString('en-US', {
327
- month: 'short',
328
- day: 'numeric',
329
- year: 'numeric'
330
- }) : 'Unknown';
331
- const shortId = commit.commitId?.substring(0, 8) || 'Unknown';
332
- result += `### ${index + 1}. ${title}\n\n`;
333
- // Inline metadata (concise, one line)
334
- result += `**\`${shortId}\`** by **${author}** on ${shortDate}`;
335
- // Add file changes if available
336
- if (commit.changeCounts) {
337
- const changes = commit.changeCounts;
338
- const changesParts = [];
339
- if (changes.Add > 0)
340
- changesParts.push(`+${changes.Add}`);
341
- if (changes.Edit > 0)
342
- changesParts.push(`~${changes.Edit}`);
343
- if (changes.Delete > 0)
344
- changesParts.push(`-${changes.Delete}`);
345
- if (changesParts.length > 0) {
346
- result += ` | ${changesParts.join(' ')} files`;
347
- }
348
- }
349
- result += `\n\n`;
350
- // Add description if present (no code block, just quote)
351
- if (description) {
352
- result += `> ${description.split('\n').join('\n> ')}\n\n`;
353
- }
354
- // Add commit link if available
355
- if (commit.remoteUrl) {
356
- result += `[View Commit](${commit.remoteUrl})\n\n`;
357
- }
358
- result += `---\n\n`;
359
- });
360
- // Prepare structured content
361
- const structuredData = {
362
- repository: params.repository,
363
- itemPath: params.itemPath,
364
- commits: commits.map((commit) => ({
365
- commitId: commit.commitId,
366
- author: formatAuthor(commit.author),
367
- date: commit.author?.date || commit.committer?.date,
368
- message: commit.comment,
369
- changeCounts: commit.changeCounts,
370
- remoteUrl: commit.remoteUrl
371
- })),
372
- summary: {
373
- totalCommits: commits.length,
374
- contributors: totalAuthors
375
- }
376
- };
377
- return (0, Common_1.formatMcpResponse)(structuredData, result, false, true);
378
- }
379
- /**
380
- * List pull requests
381
- */
382
- async listPullRequests(params) {
383
- try {
384
- const pullRequests = await this.gitService.getPullRequests(params);
385
- const formattedDocument = this.formatPullRequestsTable(pullRequests, params.repository);
386
- // Calculate summary for structured content
387
- const activeCount = pullRequests.filter((pr) => pr.status === 1).length;
388
- const completedCount = pullRequests.filter((pr) => pr.status === 2).length;
389
- const abandonedCount = pullRequests.filter((pr) => pr.status === 3).length;
390
- // Return with structured content
391
- return (0, Common_1.formatMcpResponse)({
392
- repository: params.repository,
393
- pullRequests: pullRequests.map((pr) => ({
394
- id: pr.pullRequestId,
395
- title: pr.title,
396
- author: pr.createdBy?.displayName || pr.createdBy?.uniqueName,
397
- status: pr.status,
398
- createdDate: pr.creationDate,
399
- sourceBranch: pr.sourceRefName?.replace('refs/heads/', ''),
400
- targetBranch: pr.targetRefName?.replace('refs/heads/', ''),
401
- isDraft: pr.isDraft,
402
- url: pr.url
403
- })),
404
- summary: {
405
- total: pullRequests.length,
406
- byStatus: {
407
- active: activeCount,
408
- completed: completedCount,
409
- abandoned: abandonedCount
410
- }
411
- }
412
- }, formattedDocument, false, true // Enable structured content
413
- );
414
- }
415
- catch (error) {
416
- console.error('Error in listPullRequests tool:', error);
417
- return (0, Common_1.formatErrorResponse)(error);
418
- }
419
- }
420
- /**
421
- * Formats pull requests data into a detailed, LLM-friendly format
422
- * with comprehensive information including dates, draft status, and URLs
423
- */
424
- formatPullRequestsTable(data, repository) {
425
- if (!data || data.length === 0) {
426
- return `## Pull Requests\n\n**No pull requests found** in repository: ${repository}`;
427
- }
428
- // Calculate summary statistics upfront
429
- const activeCount = data.filter((pr) => pr.status === 1).length;
430
- const completedCount = data.filter((pr) => pr.status === 2).length;
431
- const abandonedCount = data.filter((pr) => pr.status === 3).length;
432
- const draftCount = data.filter((pr) => pr.isDraft).length;
433
- // START WITH SUMMARY AT TOP
434
- let result = `## Pull Requests\n\n`;
435
- result += `**${data.length} PRs** in **${repository}**`;
436
- if (activeCount > 0 || completedCount > 0 || abandonedCount > 0) {
437
- result += ` | ${activeCount} active, ${completedCount} completed, ${abandonedCount} abandoned`;
438
- }
439
- if (draftCount > 0) {
440
- result += ` | ${draftCount} draft`;
441
- }
442
- result += `\n\n---\n\n`;
443
- // DETAILED LIST
444
- data.forEach((pr, index) => {
445
- const prId = pr.pullRequestId;
446
- const title = pr.title || 'No Title';
447
- const author = pr.createdBy?.displayName || pr.createdBy?.uniqueName || 'Unknown';
448
- const status = (0, formatHelpers_1.getPrStatusString)(pr.status);
449
- const sourceBranch = pr.sourceRefName?.replace('refs/heads/', '') || '?';
450
- const targetBranch = pr.targetRefName?.replace('refs/heads/', '') || '?';
451
- const isDraft = pr.isDraft || false;
452
- // PR Header
453
- result += `### ${index + 1}. #${prId} - ${title}\n\n`;
454
- // Status and Date line
455
- result += `**Status:** ${status}`;
456
- if (isDraft) {
457
- result += ` 📝 **DRAFT**`;
458
- }
459
- if (pr.creationDate) {
460
- const relativeDate = (0, formatHelpers_1.formatRelativeDate)(pr.creationDate);
461
- const fullDate = (0, formatHelpers_1.formatFullDate)(pr.creationDate);
462
- result += ` | **Created:** ${relativeDate} (${fullDate})`;
463
- }
464
- result += `\n`;
465
- // Author and Branches line
466
- result += `**Author:** ${author} | **Branches:** \`${sourceBranch}\` → \`${targetBranch}\`\n\n`;
467
- result += `---\n\n`;
468
- });
469
- return result;
470
- }
471
- /**
472
- * Create pull request
473
- */
474
- async createPullRequest(params) {
475
- try {
476
- const pullRequest = await this.gitService.createPullRequest(params);
477
- const sourceBranch = params.sourceRefName?.replace('refs/heads/', '') || '?';
478
- const targetBranch = params.targetRefName?.replace('refs/heads/', '') || '?';
479
- let md = `## ✅ Pull Request Created\n\n`;
480
- md += `**PR #${pullRequest.pullRequestId}** - ${pullRequest.title || params.title || 'N/A'}\n`;
481
- md += `**Branches:** \`${sourceBranch}\` → \`${targetBranch}\`\n`;
482
- if (params.reviewers && params.reviewers.length > 0) {
483
- md += `**Reviewers:** ${params.reviewers.map((r) => r.displayName || r.id || r).join(', ')}\n`;
484
- }
485
- if (pullRequest.url)
486
- md += `**URL:** ${pullRequest.url}\n`;
487
- md += await this.getWorkItemReminder(params.repository, pullRequest.pullRequestId);
488
- return (0, Common_1.formatMcpResponse)(pullRequest, md, false, true);
489
- }
490
- catch (error) {
491
- console.error('Error in createPullRequest tool:', error);
492
- return (0, Common_1.formatErrorResponse)(error);
493
- }
494
- }
495
- /**
496
- * Get pull request by ID
497
- */
498
- async getPullRequest(params) {
499
- try {
500
- const pullRequest = await this.gitService.getPullRequest(params);
501
- // Check if pullRequest is null or undefined
502
- if (!pullRequest) {
503
- return {
504
- content: [
505
- {
506
- type: "text",
507
- text: `## ❌ Pull Request Not Found\n\nPull request #${params.pullRequestId} was not found in repository '${params.repository}'.\n\nPlease check:\n- The pull request ID is correct\n- The repository name/ID is correct\n- You have access permissions to the repository`
508
- }
509
- ]
510
- };
511
- }
512
- const formattedText = this.formatPullRequestText(pullRequest);
513
- return (0, Common_1.formatMcpResponse)(pullRequest, formattedText);
514
- }
515
- catch (error) {
516
- console.error('Error in getPullRequest tool:', error);
517
- return (0, Common_1.formatErrorResponse)(error);
518
- }
519
- }
520
- /**
521
- * Formats pull request data into readable text format.
522
- * Supports two modes:
523
- * - Compact (default, no include): all sections shown but large ones truncated
524
- * - Detail (include has values): only requested sections shown in full
525
- */
526
- formatPullRequestText(pullRequest) {
527
- const include = pullRequest._include;
528
- const isDetailMode = include && include.length > 0;
529
- let md = `## Pull Request #${pullRequest.pullRequestId || 'N/A'}\n\n`;
530
- md += `### ${pullRequest.title || 'N/A'}\n\n`;
531
- // Overview (always shown — small, fixed size)
532
- if (!isDetailMode || true) {
533
- md += `| Property | Value |\n|---|---|\n`;
534
- md += `| **Status** | ${(0, formatHelpers_1.getPrStatusString)(pullRequest.status)} |\n`;
535
- md += `| **Created By** | ${pullRequest.createdBy?.displayName || 'N/A'} |\n`;
536
- md += `| **Created Date** | ${pullRequest.creationDate ? (0, formatHelpers_1.formatFullDate)(pullRequest.creationDate) : 'N/A'} |\n`;
537
- md += `| **Is Draft** | ${pullRequest.isDraft ? 'Yes' : 'No'} |\n`;
538
- md += `| **Merge Status** | ${getMergeStatusLabel(pullRequest.mergeStatus)} |\n`;
539
- md += `| **Source Branch** | \`${pullRequest.sourceRefName?.replace('refs/heads/', '') || 'N/A'}\` |\n`;
540
- md += `| **Target Branch** | \`${pullRequest.targetRefName?.replace('refs/heads/', '') || 'N/A'}\` |\n`;
541
- md += `| **Repository** | ${pullRequest.repository?.name || 'N/A'} |\n`;
542
- md += `| **Source Commit** | \`${(0, formatHelpers_1.truncateText)(pullRequest.lastMergeSourceCommit?.commitId || 'N/A', 12)}\` |\n`;
543
- md += `| **Target Commit** | \`${(0, formatHelpers_1.truncateText)(pullRequest.lastMergeTargetCommit?.commitId || 'N/A', 12)}\` |\n`;
544
- if (pullRequest.closedDate) {
545
- md += `| **Closed Date** | ${(0, formatHelpers_1.formatFullDate)(pullRequest.closedDate)} |\n`;
546
- }
547
- if (pullRequest.autoCompleteSetBy?.displayName) {
548
- md += `| **Auto-Complete By** | ${pullRequest.autoCompleteSetBy.displayName} |\n`;
549
- }
550
- }
551
- // Description
552
- if (!isDetailMode) {
553
- // Compact: truncate to ~200 chars
554
- md += `\n### Description\n\n`;
555
- const desc = pullRequest.description || '_No description provided._';
556
- if (desc.length > 200) {
557
- md += `${desc.substring(0, 200)}...\n\n_[truncated — use include: ["description"] for full]_\n`;
558
- }
559
- else {
560
- md += `${desc}\n`;
561
- }
562
- }
563
- else if (include.includes('description')) {
564
- // Detail: full description
565
- md += `\n### Description\n\n`;
566
- md += `${pullRequest.description || '_No description provided._'}\n`;
567
- }
568
- // Reviewers
569
- if (!isDetailMode) {
570
- // Compact: show count + required reviewers only, truncate if >5
571
- if (pullRequest.reviewers && pullRequest.reviewers.length > 0) {
572
- md += `\n### Reviewers (${pullRequest.reviewers.length})\n\n`;
573
- const requiredReviewers = pullRequest.reviewers.filter((r) => r.isRequired);
574
- const displayReviewers = pullRequest.reviewers.length > 5
575
- ? [...requiredReviewers.slice(0, 5)]
576
- : pullRequest.reviewers;
577
- const reviewerRows = displayReviewers.map((r) => [
578
- r.displayName || 'Unknown',
579
- getVoteLabel(r.vote),
580
- r.isRequired ? 'Required' : 'Optional',
581
- ]);
582
- md += (0, formatHelpers_1.markdownTable)(['Reviewer', 'Vote', 'Type'], reviewerRows);
583
- if (pullRequest.reviewers.length > 5) {
584
- md += `\n\n_...and ${pullRequest.reviewers.length - displayReviewers.length} more — use include: ["reviewers"] for full list_\n`;
585
- }
586
- }
587
- }
588
- else if (include.includes('reviewers')) {
589
- // Detail: full reviewer list
590
- if (pullRequest.reviewers && pullRequest.reviewers.length > 0) {
591
- md += `\n### Reviewers\n\n`;
592
- const reviewerRows = pullRequest.reviewers.map((r) => [
593
- r.displayName || 'Unknown',
594
- getVoteLabel(r.vote),
595
- r.isRequired ? 'Required' : 'Optional',
596
- ]);
597
- md += (0, formatHelpers_1.markdownTable)(['Reviewer', 'Vote', 'Type'], reviewerRows);
598
- }
599
- }
600
- // Labels/Tags (always shown — small)
601
- if (pullRequest.labels && pullRequest.labels.length > 0) {
602
- md += `\n### Labels\n\n`;
603
- md += pullRequest.labels.map((l) => `\`${l.name || l}\``).join(', ') + '\n';
604
- }
605
- // Policy Checks (shown in compact mode always, or when explicitly requested)
606
- if (!isDetailMode || include.includes('policies')) {
607
- md += this.formatPolicyChecksSection(pullRequest.policyEvaluations);
608
- }
609
- // Work Items
610
- if (!isDetailMode) {
611
- // Compact: show count + IDs only, truncate if >3
612
- if (pullRequest.workItems && pullRequest.workItems.length > 0) {
613
- md += `\n### Work Items (${pullRequest.workItems.length})\n\n`;
614
- const displayItems = pullRequest.workItems.slice(0, 3);
615
- const wiRows = displayItems.map((wi) => [
616
- `#${wi.id}`,
617
- (0, formatHelpers_1.truncateText)(wi.title || 'N/A', 50),
618
- wi.type || '-',
619
- wi.state || '-',
620
- ]);
621
- md += (0, formatHelpers_1.markdownTable)(['ID', 'Title', 'Type', 'State'], wiRows);
622
- if (pullRequest.workItems.length > 3) {
623
- md += `\n\n_...and ${pullRequest.workItems.length - 3} more — use include: ["workItems"] for full list_\n`;
624
- }
625
- }
626
- }
627
- else if (include.includes('workItems')) {
628
- // Detail: full work items
629
- if (pullRequest.workItems && pullRequest.workItems.length > 0) {
630
- md += `\n### Associated Work Items\n\n`;
631
- const wiRows = pullRequest.workItems.map((wi) => [
632
- `#${wi.id}`,
633
- wi.title || 'N/A',
634
- wi.type || '-',
635
- wi.state || '-',
636
- wi.assignedTo || '-',
637
- ]);
638
- md += (0, formatHelpers_1.markdownTable)(['ID', 'Title', 'Type', 'State', 'Assigned To'], wiRows);
639
- }
640
- }
641
- // Files hint (compact only — files have dedicated tools)
642
- if (!isDetailMode) {
643
- md += `\n### Files\n\n_Use \`getPullRequestChangesCount\` or \`getAllPullRequestChanges\` for file details._\n`;
644
- }
645
- // Completion Options
646
- if (!isDetailMode) {
647
- // Compact: only show if auto-complete is set
648
- if (pullRequest.autoCompleteSetBy?.displayName && pullRequest.completionOptions) {
649
- md += `\n### Completion Options\n\n`;
650
- md += `| Option | Value |\n|---|---|\n`;
651
- if (pullRequest.completionOptions.mergeStrategy !== undefined) {
652
- md += `| **Merge Strategy** | ${getMergeStrategyLabel(pullRequest.completionOptions.mergeStrategy)} |\n`;
653
- }
654
- if (pullRequest.completionOptions.deleteSourceBranch !== undefined) {
655
- md += `| **Delete Source Branch** | ${pullRequest.completionOptions.deleteSourceBranch ? 'Yes' : 'No'} |\n`;
656
- }
657
- }
658
- }
659
- else if (include.includes('completionOptions')) {
660
- // Detail: full completion options
661
- if (pullRequest.completionOptions) {
662
- md += `\n### Completion Options\n\n`;
663
- md += `| Option | Value |\n|---|---|\n`;
664
- if (pullRequest.completionOptions.mergeStrategy !== undefined) {
665
- md += `| **Merge Strategy** | ${getMergeStrategyLabel(pullRequest.completionOptions.mergeStrategy)} |\n`;
666
- }
667
- if (pullRequest.completionOptions.deleteSourceBranch !== undefined) {
668
- md += `| **Delete Source Branch** | ${pullRequest.completionOptions.deleteSourceBranch ? 'Yes' : 'No'} |\n`;
669
- }
670
- if (pullRequest.completionOptions.mergeCommitMessage) {
671
- md += `| **Merge Commit Message** | ${pullRequest.completionOptions.mergeCommitMessage} |\n`;
672
- }
673
- }
674
- }
675
- return md;
676
- }
677
- /**
678
- * Formats policy evaluation records into a markdown section
679
- */
680
- formatPolicyChecksSection(evaluations) {
681
- if (!evaluations || evaluations.length === 0) {
682
- return '\n### Policy Checks\n\n_No policy evaluations found._\n';
683
- }
684
- let md = '\n### Policy Checks\n\n';
685
- const rows = evaluations.map((ev) => {
686
- const policyName = ev.configuration?.type?.displayName || ev.configuration?.settings?.displayName || 'Unknown Policy';
687
- const isBlocking = ev.configuration?.isBlocking;
688
- const status = ev.status;
689
- const statusLabel = getPolicyStatusLabel(status);
690
- return [policyName, statusLabel, isBlocking ? 'Yes' : 'No'];
691
- });
692
- md += (0, formatHelpers_1.markdownTable)(['Policy', 'Status', 'Required'], rows);
693
- // Summary: count required passing
694
- const required = evaluations.filter((ev) => ev.configuration?.isBlocking);
695
- const requiredPassing = required.filter((ev) => ev.status === 2); // Approved = 2
696
- if (required.length > 0) {
697
- md += `\n\n**${requiredPassing.length}/${required.length} required checks passing**\n`;
698
- }
699
- return md;
700
- }
701
- /**
702
- * Get pull request comments
703
- */
704
- async getPullRequestComments(params) {
705
- try {
706
- const comments = await this.gitService.getPullRequestComments(params);
707
- const formattedDocument = this.formatPullRequestCommentsDocument(comments, params.pullRequestId);
708
- // Handle both array and object with value property
709
- const threads = Array.isArray(comments) ? comments : (comments.value || []);
710
- // Prepare structured content
711
- const structuredData = {
712
- pullRequestId: params.pullRequestId,
713
- threads: threads.map((thread) => ({
714
- id: thread.id,
715
- status: this.getThreadStatusLabel(thread.status),
716
- type: thread.properties?.CodeReviewThreadType?.$value,
717
- filePath: thread.threadContext?.filePath,
718
- lineStart: thread.threadContext?.rightFileStart?.line,
719
- lineEnd: thread.threadContext?.rightFileEnd?.line,
720
- comments: (thread.comments || []).map((comment) => ({
721
- author: comment.author?.displayName || comment.author?.uniqueName,
722
- content: comment.content,
723
- publishedDate: comment.publishedDate,
724
- isReply: (comment.parentCommentId ?? 0) > 0,
725
- likesCount: comment.usersLiked?.length || 0
726
- }))
727
- })),
728
- summary: {
729
- totalThreads: threads.length,
730
- totalComments: threads.reduce((sum, t) => sum + (t.comments?.length || 0), 0),
731
- active: threads.filter((t) => this.getThreadStatusLabel(t.status) === 'active').length,
732
- fixed: threads.filter((t) => this.getThreadStatusLabel(t.status) === 'fixed').length,
733
- closed: threads.filter((t) => this.getThreadStatusLabel(t.status) === 'closed').length,
734
- byDesign: threads.filter((t) => this.getThreadStatusLabel(t.status) === 'byDesign').length,
735
- wontFix: threads.filter((t) => this.getThreadStatusLabel(t.status) === 'wontFix').length,
736
- pending: threads.filter((t) => this.getThreadStatusLabel(t.status) === 'pending').length
737
- }
738
- };
739
- return (0, Common_1.formatMcpResponse)(structuredData, formattedDocument, false, true);
740
- }
741
- catch (error) {
742
- console.error('Error in getPullRequestComments tool:', error);
743
- return (0, Common_1.formatErrorResponse)(error);
744
- }
745
- }
746
- /**
747
- * Formats pull request comments data into a concise document format
748
- */
749
- formatPullRequestCommentsDocument(data, pullRequestId) {
750
- if (!data || (!Array.isArray(data) && !data.length && !data.value)) {
751
- return `## PR Comments\n\n**No comments found** in PR #${pullRequestId}`;
752
- }
753
- // Handle both array and object with value property
754
- const threads = Array.isArray(data) ? data : (data.value || []);
755
- if (threads.length === 0) {
756
- return `## PR Comments\n\n**No comments found** in PR #${pullRequestId}`;
757
- }
758
- // Calculate summary statistics upfront
759
- const commentCount = threads.reduce((sum, thread) => sum + (thread.comments?.length || 0), 0);
760
- const codeReviewCount = threads.filter((t) => t.properties?.CodeReviewThreadType?.$value === 'CodeReview').length;
761
- const generalCount = threads.filter((t) => t.properties?.CodeReviewThreadType?.$value === 'General').length;
762
- const systemCount = threads.length - codeReviewCount - generalCount;
763
- // Calculate status counts
764
- const activeCount = threads.filter((t) => this.getThreadStatusLabel(t.status) === 'active').length;
765
- const fixedCount = threads.filter((t) => this.getThreadStatusLabel(t.status) === 'fixed').length;
766
- const closedCount = threads.filter((t) => this.getThreadStatusLabel(t.status) === 'closed').length;
767
- const resolvedCount = fixedCount + closedCount;
768
- // START WITH SUMMARY AT TOP
769
- let document = `## PR #${pullRequestId} Comments\n\n`;
770
- document += `**${threads.length} threads** | **${commentCount} comments**`;
771
- if (codeReviewCount > 0 || generalCount > 0 || systemCount > 0) {
772
- document += ` | ${codeReviewCount} code review, ${generalCount} general, ${systemCount} system`;
773
- }
774
- document += `\n`;
775
- // Status breakdown
776
- const statusParts = [];
777
- if (activeCount > 0)
778
- statusParts.push(`${activeCount} active`);
779
- if (resolvedCount > 0)
780
- statusParts.push(`${resolvedCount} resolved`);
781
- const pendingCount = threads.filter((t) => this.getThreadStatusLabel(t.status) === 'pending').length;
782
- if (pendingCount > 0)
783
- statusParts.push(`${pendingCount} pending`);
784
- if (statusParts.length > 0) {
785
- document += `**Status:** ${statusParts.join(', ')}\n`;
786
- }
787
- document += `\n---\n\n`;
788
- threads.forEach((thread, index) => {
789
- document += this.formatCommentThread(thread, index + 1);
790
- document += `\n---\n\n`;
791
- });
792
- return document;
793
- }
794
- /**
795
- * Formats a single comment thread
796
- */
797
- formatCommentThread(thread, threadNumber) {
798
- const threadType = thread.properties?.CodeReviewThreadType?.$value || 'Unknown';
799
- const typeLabel = this.getThreadTypeDescription(threadType);
800
- const statusLabel = this.getThreadStatusLabel(thread.status);
801
- let threadDoc = `### ${threadNumber}. [${typeLabel}] (Thread #${thread.id} — ${statusLabel})`;
802
- // Add file context if available (inline)
803
- if (thread.threadContext?.filePath) {
804
- const filePath = thread.threadContext.filePath;
805
- const lineStart = thread.threadContext.rightFileStart?.line;
806
- const lineEnd = thread.threadContext.rightFileEnd?.line;
807
- threadDoc += ` | \`${filePath}\``;
808
- if (lineStart && lineEnd) {
809
- threadDoc += `:${lineStart}-${lineEnd}`;
810
- }
811
- }
812
- threadDoc += `\n\n`;
813
- // Format comments in the thread
814
- if (thread.comments && thread.comments.length > 0) {
815
- thread.comments.forEach((comment) => {
816
- threadDoc += this.formatSingleComment(comment);
817
- });
818
- }
819
- return threadDoc;
820
- }
821
- /**
822
- * Formats a single comment
823
- */
824
- formatSingleComment(comment) {
825
- const author = comment.author?.displayName || 'Unknown';
826
- const content = comment.content || 'No content';
827
- const isReply = comment.parentCommentId > 0;
828
- let commentDoc = `${isReply ? ' ' : ''}**${isReply ? '↳ ' : ''}${author}:**`;
829
- // Add likes if any
830
- if (comment.usersLiked && comment.usersLiked.length > 0) {
831
- commentDoc += ` 👍${comment.usersLiked.length}`;
832
- }
833
- commentDoc += `\n${isReply ? ' ' : ''}> ${content}\n\n`;
834
- return commentDoc;
835
- }
836
- /**
837
- * Gets a human-readable description for thread types
838
- */
839
- getThreadTypeDescription(threadType) {
840
- switch (threadType) {
841
- case 'CodeReview': return '💬 Code Review';
842
- case 'General': return '💭 General';
843
- case 'RefUpdate': return '🔄 Branch Update';
844
- case 'ReviewersUpdate': return '👥 Reviewers Change';
845
- case 'IsDraftUpdate': return '📝 Draft Change';
846
- default: return threadType || 'Unknown';
847
- }
848
- }
849
- /**
850
- * Maps Azure DevOps thread status enum to a human-readable label.
851
- * Status enum: 0=unknown, 1=active, 2=fixed, 3=wontFix, 4=closed, 5=byDesign, 6=pending
852
- */
853
- getThreadStatusLabel(status) {
854
- switch (status) {
855
- case 1: return 'active';
856
- case 2: return 'fixed';
857
- case 3: return 'wontFix';
858
- case 4: return 'closed';
859
- case 5: return 'byDesign';
860
- case 6: return 'pending';
861
- default: return 'unknown';
862
- }
863
- }
864
- /**
865
- * Approve pull request
866
- */
867
- async approvePullRequest(params) {
868
- try {
869
- const result = await this.gitService.approvePullRequest(params);
870
- const md = `## ✅ Pull Request Approved\n\n**PR #${params.pullRequestId}** in \`${params.repository}\` has been approved.`;
871
- return (0, Common_1.formatMcpResponse)(result, md, false, true);
872
- }
873
- catch (error) {
874
- console.error('Error in approvePullRequest tool:', error);
875
- return (0, Common_1.formatErrorResponse)(error);
876
- }
877
- }
878
- /**
879
- * Merge pull request
880
- */
881
- async mergePullRequest(params) {
882
- try {
883
- const result = await this.gitService.mergePullRequest(params);
884
- let md = `## ✅ Pull Request Merged\n\n**PR #${params.pullRequestId}** in \`${params.repository}\``;
885
- if (params.mergeStrategy)
886
- md += ` | Strategy: ${params.mergeStrategy}`;
887
- md += '\n';
888
- return (0, Common_1.formatMcpResponse)(result, md, false, true);
889
- }
890
- catch (error) {
891
- console.error('Error in mergePullRequest tool:', error);
892
- return (0, Common_1.formatErrorResponse)(error);
893
- }
894
- }
895
- /**
896
- * Add inline comment to pull request
897
- */
898
- async addPullRequestInlineComment(params) {
899
- try {
900
- const result = await this.gitService.addPullRequestInlineComment(params);
901
- // Build concise confirmation message
902
- const fileName = params.path.split('/').pop() || params.path;
903
- const message = `## Comment Added\n\n**Type:** Inline comment\n**PR:** #${params.pullRequestId}\n**File:** \`${fileName}\`\n**Line:** ${params.position.line}\n\n✅ Your comment has been posted to the Files tab.`;
904
- return (0, Common_1.formatMcpResponse)(result, message, false, true);
905
- }
906
- catch (error) {
907
- console.error('Error in addPullRequestInlineComment tool:', error);
908
- // Provide enhanced user-friendly error responses
909
- if (error instanceof Error) {
910
- // File not in PR changes
911
- if (error.message.includes('not part of the changes')) {
912
- return {
913
- content: [
914
- {
915
- type: "text",
916
- text: `## ❌ Cannot Add Inline Comment\n\n${error.message}\n\n💡 **Next Steps:**\n\n1. Use \`getPullRequestFileChanges\` to see which files are available\n2. Check the exact file paths in the PR changes\n3. Ensure you're using the correct file path format`
917
- }
918
- ]
919
- };
920
- }
921
- // Line number or position issues
922
- if (error.message.includes('line number') ||
923
- error.message.includes('getPullRequestFileChanges') ||
924
- error.message.includes('line position')) {
925
- return {
926
- content: [
927
- {
928
- type: "text",
929
- text: `## ❌ Invalid Line Position\n\n${error.message}\n\n### 🔍 **How to Find the Correct Line:**\n\n\`\`\`\ngetPullRequestFileChanges repository="${params.repository}" pullRequestId=${params.pullRequestId} path="${params.path}"\n\`\`\`\n\nThis will show you:\n\n**📁 For NEWLY ADDED files:**\n- All lines are available for comments (1, 2, 3, ... N)\n- Diff shows: \`+1: line content\`, \`+2: line content\`, etc.\n- You can comment on ANY line number from 1 to the total lines\n\n**📝 For MODIFIED files:**\n- Only changed line ranges can be commented on\n- Look for lines with \`+\` (added) or context lines\n- Line numbers correspond to the new version of the file\n\n**🗑️ For DELETED files:**\n- Only the deleted lines can be commented on\n- Diff shows: \`-1: deleted content\`, \`-2: deleted content\`, etc.`
930
- }
931
- ]
932
- };
933
- }
934
- }
935
- return (0, Common_1.formatErrorResponse)(error);
936
- }
937
- }
938
- /**
939
- * Add file comment to pull request
940
- */
941
- async addPullRequestFileComment(params) {
942
- try {
943
- const result = await this.gitService.addPullRequestFileComment(params);
944
- // Build concise confirmation message
945
- const fileName = params.path.split('/').pop() || params.path;
946
- const message = `## Comment Added\n\n**Type:** File-level comment\n**PR:** #${params.pullRequestId}\n**File:** \`${fileName}\`\n\n✅ Your comment has been posted to the Files tab.`;
947
- return (0, Common_1.formatMcpResponse)(result, message, false, true);
948
- }
949
- catch (error) {
950
- console.error('Error in addPullRequestFileComment tool:', error);
951
- return (0, Common_1.formatErrorResponse)(error);
952
- }
953
- }
954
- /**
955
- * Add general comment to pull request
956
- */
957
- async addPullRequestComment(params) {
958
- try {
959
- const result = await this.gitService.addPullRequestComment(params);
960
- // Build concise confirmation message
961
- const message = `## Comment Added\n\n**Type:** General PR comment\n**PR:** #${params.pullRequestId}\n\n✅ Your comment has been posted to the Overview tab.`;
962
- return (0, Common_1.formatMcpResponse)(result, message, false, true);
963
- }
964
- catch (error) {
965
- console.error('Error in addPullRequestComment tool:', error);
966
- return (0, Common_1.formatErrorResponse)(error);
967
- }
968
- }
969
- /**
970
- * Get pull request file changes
971
- */
972
- async getPullRequestFileChanges(params) {
973
- try {
974
- const changes = await this.gitService.getPullRequestFileChanges(params);
975
- const formattedContent = this.formatPullRequestFileChanges(changes);
976
- return (0, Common_1.formatMcpResponse)(changes, formattedContent, false, true); // Enable structured content
977
- }
978
- catch (error) {
979
- console.error('Error in getPullRequestFileChanges tool:', error);
980
- return (0, Common_1.formatErrorResponse)(error);
981
- }
982
- }
983
- /**
984
- * Get pull request changes count
985
- */
986
- async getPullRequestChangesCount(params) {
987
- try {
988
- const count = await this.gitService.getPullRequestChangesCount(params);
989
- const total = count?.totalChanges || count?.totalFiles || count?.total || count?.count || 0;
990
- const modified = count?.modifiedFiles || count?.modified || 0;
991
- const added = count?.addedFiles || count?.added || 0;
992
- const deleted = count?.deletedFiles || count?.deleted || 0;
993
- let md = `## PR #${params.pullRequestId} Changes\n\n`;
994
- md += `**${total} files**`;
995
- const parts = [];
996
- if (modified > 0)
997
- parts.push(`${modified} modified`);
998
- if (added > 0)
999
- parts.push(`${added} added`);
1000
- if (deleted > 0)
1001
- parts.push(`${deleted} deleted`);
1002
- if (parts.length > 0)
1003
- md += `: ${parts.join(', ')}`;
1004
- return (0, Common_1.formatMcpResponse)(count, md, false, true);
1005
- }
1006
- catch (error) {
1007
- console.error('Error in getPullRequestChangesCount tool:', error);
1008
- return (0, Common_1.formatErrorResponse)(error);
1009
- }
1010
- }
1011
- /**
1012
- * Get all pull request changes
1013
- */
1014
- async getAllPullRequestChanges(params) {
1015
- try {
1016
- const changes = await this.gitService.getAllPullRequestChanges(params);
1017
- const formattedTable = this.formatPullRequestChangesTable(changes);
1018
- // Calculate summary for structured content
1019
- const changeList = changes.changes || [];
1020
- const addedCount = changeList.filter((c) => c.changeType === 1).length;
1021
- const modifiedCount = changeList.filter((c) => c.changeType === 2).length;
1022
- const deletedCount = changeList.filter((c) => c.changeType === 3).length;
1023
- // Return with structured content
1024
- return (0, Common_1.formatMcpResponse)({
1025
- pullRequestId: params.pullRequestId,
1026
- changes: changeList.map((change) => ({
1027
- path: change.item?.path,
1028
- changeType: change.changeType,
1029
- size: change.item?.size
1030
- })),
1031
- summary: {
1032
- totalChanges: changes.totalCount || changeList.length,
1033
- byType: {
1034
- added: addedCount,
1035
- modified: modifiedCount,
1036
- deleted: deletedCount
1037
- }
1038
- }
1039
- }, formattedTable, false, true // Enable structured content
1040
- );
1041
- }
1042
- catch (error) {
1043
- console.error('Error in getAllPullRequestChanges tool:', error);
1044
- return (0, Common_1.formatErrorResponse)(error);
1045
- }
1046
- }
1047
- /**
1048
- * Formats pull request changes data into a concise table format
1049
- */
1050
- formatPullRequestChangesTable(data) {
1051
- if (!data || !data.changes || data.changes.length === 0) {
1052
- return "No changes found in this pull request.";
1053
- }
1054
- const changes = data.changes;
1055
- // Calculate summary statistics upfront (to put at top)
1056
- const addedCount = changes.filter((c) => c.changeType === 1).length;
1057
- const modifiedCount = changes.filter((c) => c.changeType === 2).length;
1058
- const deletedCount = changes.filter((c) => c.changeType === 3).length;
1059
- const totalCount = data.totalCount || changes.length;
1060
- // START WITH SUMMARY AT TOP
1061
- let result = `## PR Changes\n\n`;
1062
- result += `**${totalCount} files:** ${modifiedCount} modified, ${addedCount} added, ${deletedCount} deleted\n\n`;
1063
- // Optional pagination info
1064
- if (data.totalCount && data.totalCount > changes.length) {
1065
- result += `⚠️ Showing ${changes.length} of ${totalCount} files (use pagination for more)\n\n`;
1066
- }
1067
- result += `---\n\n`;
1068
- // Simplified 3-column table
1069
- result += `| # | Path | Change |\n`;
1070
- result += `|---|------|--------|\n`;
1071
- changes.forEach((change, index) => {
1072
- const changeNum = index + 1;
1073
- const changeType = (0, formatHelpers_1.getChangeTypeString)(change.changeType);
1074
- const filePath = change.item?.path || 'N/A';
1075
- result += `| ${changeNum} | \`${filePath}\` | ${changeType} |\n`;
1076
- });
1077
- return result;
1078
- }
1079
- /**
1080
- * Formats file content with line numbers (arrow notation style) and metadata
1081
- * Truncates to 200 lines or 8K characters, whichever comes first
1082
- */
1083
- formatFileContent(data, path, params) {
1084
- if (!data || !data.content) {
1085
- return `## File: \`${path}\`\n\n*No content available*`;
1086
- }
1087
- const content = data.content;
1088
- const metadata = data.metadata || {};
1089
- const lines = content.split('\n');
1090
- const totalLines = metadata.totalLines || lines.length;
1091
- const startLine = metadata.startLine || 1;
1092
- const endLine = metadata.endLine || lines.length;
1093
- const actualLineCount = lines.length;
1094
- // Calculate file size
1095
- const sizeInBytes = Buffer.byteLength(content, 'utf8');
1096
- const sizeInKB = (sizeInBytes / 1024).toFixed(2);
1097
- // Truncation limits
1098
- const maxLines = 200;
1099
- const maxChars = 8000;
1100
- // Determine truncation
1101
- let displayLines = lines;
1102
- let truncated = false;
1103
- let truncatedDueTo = 'none';
1104
- let effectiveEndLine = endLine;
1105
- // First apply line limit
1106
- if (actualLineCount > maxLines) {
1107
- displayLines = lines.slice(0, maxLines);
1108
- truncated = true;
1109
- truncatedDueTo = 'lineLimit';
1110
- effectiveEndLine = startLine + maxLines - 1;
1111
- }
1112
- // Then check character limit
1113
- let formattedContent = displayLines.map((line, idx) => {
1114
- const lineNum = startLine + idx;
1115
- return `${lineNum.toString().padStart(6, ' ')}→${line}`;
1116
- }).join('\n');
1117
- if (formattedContent.length > maxChars) {
1118
- // Find how many lines fit within char limit
1119
- let charCount = 0;
1120
- let linesFit = 0;
1121
- for (let i = 0; i < displayLines.length; i++) {
1122
- const lineNum = startLine + i;
1123
- const formattedLine = `${lineNum.toString().padStart(6, ' ')}→${displayLines[i]}\n`;
1124
- if (charCount + formattedLine.length > maxChars) {
1125
- break;
1126
- }
1127
- charCount += formattedLine.length;
1128
- linesFit++;
1129
- }
1130
- displayLines = displayLines.slice(0, linesFit);
1131
- truncated = true;
1132
- truncatedDueTo = 'charLimit';
1133
- effectiveEndLine = startLine + linesFit - 1;
1134
- // Reformat with adjusted lines
1135
- formattedContent = displayLines.map((line, idx) => {
1136
- const lineNum = startLine + idx;
1137
- return `${lineNum.toString().padStart(6, ' ')}→${line}`;
1138
- }).join('\n');
1139
- }
1140
- // Build header with metadata
1141
- let result = `## File: \`${path}\`\n\n`;
1142
- if (startLine === 1 && effectiveEndLine >= totalLines && !truncated) {
1143
- result += `**${totalLines} lines** | **${sizeInKB} KB** | **Encoding:** utf-8`;
1144
- }
1145
- else {
1146
- result += `**Lines ${startLine}-${effectiveEndLine} of ${totalLines}** | **${sizeInKB} KB** | **Encoding:** utf-8`;
1147
- }
1148
- if (truncated) {
1149
- result += `\n\n> ⚠️ **Truncated** - `;
1150
- if (truncatedDueTo === 'lineLimit') {
1151
- result += `showing first ${maxLines} lines of ${actualLineCount} requested`;
1152
- }
1153
- else {
1154
- result += `content exceeds ${(maxChars / 1024).toFixed(1)}K character limit`;
1155
- }
1156
- result += `\n> 💡 **Tip:** Use \`startLine\` and \`lineCount\` parameters to view specific ranges`;
1157
- }
1158
- else if (effectiveEndLine < totalLines) {
1159
- result += `\n\n> 💡 **More content available:** Use \`startLine=${effectiveEndLine + 1}\` to continue reading`;
1160
- }
1161
- result += `\n\n---\n\n`;
1162
- // Format content with arrow notation line numbers
1163
- result += `\`\`\`\n${formattedContent}\n\`\`\`\n`;
1164
- // Add usage hints for large files
1165
- if (totalLines > maxLines || truncated) {
1166
- result += `\n**Navigation hints:**\n`;
1167
- if (effectiveEndLine < totalLines) {
1168
- result += `- Next range: \`startLine=${effectiveEndLine + 1}\`, \`lineCount=${maxLines}\`\n`;
1169
- }
1170
- if (startLine > 1) {
1171
- result += `- Previous range: \`startLine=${Math.max(1, startLine - maxLines)}\`, \`lineCount=${maxLines}\`\n`;
1172
- }
1173
- result += `- Specific range: \`startLine=<line>\`, \`lineCount=<count>\` (max ${maxLines} lines)\n`;
1174
- }
1175
- return result;
1176
- }
1177
- /**
1178
- * Formats pull request file changes data into a detailed, readable format with diff content
1179
- */
1180
- formatPullRequestFileChanges(data) {
1181
- if (!data || !data.changeEntries || data.changeEntries.length === 0) {
1182
- return "No file changes found in this pull request.";
1183
- }
1184
- const changes = data.changeEntries;
1185
- const MAX_DIFF_LINES = 20; // Limit diff preview to first 20 lines
1186
- // Helper function to convert change type to short label
1187
- const getChangeTypeLabel = (changeType) => {
1188
- switch (changeType) {
1189
- case 1: return 'Added';
1190
- case 2: return 'Modified';
1191
- case 3: return 'Deleted';
1192
- default: return 'Unknown';
1193
- }
1194
- };
1195
- // Calculate summary statistics
1196
- const addedCount = changes.filter((c) => c.changeType === 1).length;
1197
- const modifiedCount = changes.filter((c) => c.changeType === 2).length;
1198
- const deletedCount = changes.filter((c) => c.changeType === 3).length;
1199
- // Calculate total line changes and prepare file info
1200
- let totalAdditions = 0;
1201
- let totalDeletions = 0;
1202
- const fileInfos = [];
1203
- changes.forEach((change) => {
1204
- const filePath = change.item?.path || 'N/A';
1205
- let added = 0;
1206
- let removed = 0;
1207
- let diffLines = 0;
1208
- if (change.diffContent) {
1209
- const lines = change.diffContent.split('\n');
1210
- diffLines = lines.length;
1211
- added = lines.filter((line) => line.startsWith('+') && !line.startsWith('+++')).length;
1212
- removed = lines.filter((line) => line.startsWith('-') && !line.startsWith('---')).length;
1213
- totalAdditions += added;
1214
- totalDeletions += removed;
1215
- }
1216
- fileInfos.push({
1217
- path: filePath,
1218
- type: getChangeTypeLabel(change.changeType),
1219
- added,
1220
- removed,
1221
- diffLines
1222
- });
1223
- });
1224
- // Build output starting with compact summary
1225
- let result = `## PR File Changes (${changes.length} files)\n\n`;
1226
- // Compact summary line
1227
- const parts = [];
1228
- if (modifiedCount > 0)
1229
- parts.push(`${modifiedCount} modified`);
1230
- if (addedCount > 0)
1231
- parts.push(`${addedCount} added`);
1232
- if (deletedCount > 0)
1233
- parts.push(`${deletedCount} deleted`);
1234
- result += `${parts.join(' • ')}`;
1235
- if (totalAdditions > 0 || totalDeletions > 0) {
1236
- result += ` | **+${totalAdditions} -${totalDeletions}** lines`;
1237
- }
1238
- result += `\n\n`;
1239
- if (data.totalChanges && data.processedChanges && data.totalChanges > data.processedChanges) {
1240
- result += `> Showing ${data.processedChanges} of ${data.totalChanges} files\n\n`;
1241
- }
1242
- // Compact file list
1243
- result += `### Files\n`;
1244
- fileInfos.forEach((info, idx) => {
1245
- const changeIndicator = info.added > 0 || info.removed > 0 ? ` (+${info.added} -${info.removed})` : '';
1246
- result += `${idx + 1}. \`${info.path}\` - ${info.type}${changeIndicator}\n`;
1247
- });
1248
- result += `\n---\n\n`;
1249
- result += `### Diff Preview\n\n`;
1250
- // Show diff previews (truncated)
1251
- changes.forEach((change, index) => {
1252
- const filePath = change.item?.path || 'N/A';
1253
- const info = fileInfos[index];
1254
- const changeIndicator = info.added > 0 || info.removed > 0 ? `(+${info.added} -${info.removed})` : '';
1255
- result += `**${index + 1}. ${filePath}** ${changeIndicator}\n`;
1256
- if (change.diffContent) {
1257
- const lines = change.diffContent.split('\n');
1258
- const totalLines = lines.length;
1259
- const truncated = totalLines > MAX_DIFF_LINES;
1260
- const displayLines = truncated ? lines.slice(0, MAX_DIFF_LINES) : lines;
1261
- result += `\`\`\`diff\n${displayLines.join('\n')}\n\`\`\`\n`;
1262
- if (truncated) {
1263
- const hiddenLines = totalLines - MAX_DIFF_LINES;
1264
- result += `> ... ${hiddenLines} more lines hidden\n`;
1265
- }
1266
- }
1267
- else {
1268
- result += `*No diff available*\n`;
1269
- }
1270
- result += `\n`;
1271
- });
1272
- return result;
1273
- }
1274
- /**
1275
- * Get available lines for inline comments in a PR file
1276
- */
1277
- async getPullRequestFileLines(params) {
1278
- try {
1279
- const changes = await this.gitService.getPullRequestFileChanges(params);
1280
- if (!changes || !changes.changes || changes.changes.length === 0) {
1281
- return {
1282
- content: [
1283
- {
1284
- type: "text",
1285
- text: `## 📝 No Changes Found\n\nNo changes found for file '${params.path}' in PR #${params.pullRequestId}.\n\n💡 **Tip:** Use \`getPullRequestFileChanges\` without the path parameter to see all changed files in this PR.`
1286
- }
1287
- ]
1288
- };
1289
- }
1290
- // Extract line information for commenting guidance
1291
- let linesInfo = `## 📍 Available Lines for Inline Comments\n\n**File:** \`${params.path}\`\n**PR:** #${params.pullRequestId}\n\n`;
1292
- changes.changes.forEach((change, index) => {
1293
- if (change.item && change.item.gitObjectType === 'blob') {
1294
- linesInfo += `### Change ${index + 1}:\n`;
1295
- linesInfo += `- **Type:** ${change.changeType === 1 ? 'ADDED (New file)' : change.changeType === 2 ? 'MODIFIED' : change.changeType === 3 ? 'DELETED' : 'Unknown'}\n`;
1296
- if (change.item.path) {
1297
- linesInfo += `- **Path:** \`${change.item.path}\`\n`;
1298
- }
1299
- // Provide specific guidance based on change type
1300
- if (change.changeType === 1) {
1301
- // Newly added file
1302
- linesInfo += `\n💡 **For NEW files:** All lines are available for commenting!\n`;
1303
- linesInfo += `- Line numbers: 1, 2, 3, ... up to the total lines in the file\n`;
1304
- linesInfo += `- Example: Use line 1 for the first line, line 2 for the second line, etc.\n`;
1305
- linesInfo += `- The diff will show: \`+1: content\`, \`+2: content\`, etc.\n`;
1306
- }
1307
- else if (change.changeType === 2) {
1308
- // Modified file
1309
- linesInfo += `\n💡 **For MODIFIED files:** Only changed lines can be commented on\n`;
1310
- linesInfo += `- Look for lines with \`+\` (added) or context lines in the diff\n`;
1311
- linesInfo += `- Line numbers correspond to the new version of the file\n`;
1312
- }
1313
- else if (change.changeType === 3) {
1314
- // Deleted file
1315
- linesInfo += `\n💡 **For DELETED files:** Only deleted lines can be commented on\n`;
1316
- linesInfo += `- The diff will show: \`-1: content\`, \`-2: content\`, etc.\n`;
1317
- }
1318
- linesInfo += `\n`;
1319
- }
1320
- });
1321
- linesInfo += `---\n\n**Next Steps:**\n1. Use \`getPullRequestFileChanges\` to see the actual code diff\n2. Look for line numbers in the diff (lines starting with + or - or context lines)\n3. Use those line numbers for \`addPullRequestInlineComment\``;
1322
- return {
1323
- content: [
1324
- {
1325
- type: "text",
1326
- text: linesInfo
1327
- }
1328
- ]
1329
- };
1330
- }
1331
- catch (error) {
1332
- console.error('Error in getPullRequestFileLines tool:', error);
1333
- return (0, Common_1.formatErrorResponse)(error);
1334
- }
1335
- }
1336
- // ── New PR Enhancement Tools ─────────────────────────────────
1337
- /**
1338
- * Update pull request properties (title, description, status, auto-complete, draft)
1339
- */
1340
- async updatePullRequest(params) {
1341
- try {
1342
- const result = await this.gitService.updatePullRequest(params);
1343
- let md = `## Pull Request #${params.pullRequestId} Updated\n\n`;
1344
- md += `| Property | Value |\n|---|---|\n`;
1345
- md += `| **Title** | ${result.title || '-'} |\n`;
1346
- md += `| **Status** | ${(0, formatHelpers_1.getPrStatusString)(result.status)} |\n`;
1347
- md += `| **Is Draft** | ${result.isDraft ? 'Yes' : 'No'} |\n`;
1348
- if (result.autoCompleteSetBy?.displayName) {
1349
- md += `| **Auto-Complete By** | ${result.autoCompleteSetBy.displayName} |\n`;
1350
- }
1351
- md += await this.getWorkItemReminder(params.repository, params.pullRequestId);
1352
- return (0, Common_1.formatMcpResponse)(result, md, false, true);
1353
- }
1354
- catch (error) {
1355
- return (0, Common_1.formatErrorResponse)(error);
1356
- }
1357
- }
1358
- /**
1359
- * Add or remove reviewers on a pull request
1360
- */
1361
- async updatePullRequestReviewers(params) {
1362
- try {
1363
- const result = await this.gitService.updatePullRequestReviewers(params);
1364
- let md = `## PR #${params.pullRequestId} - Reviewers Updated\n\n`;
1365
- if (result.added.length > 0) {
1366
- md += `**Added ${result.added.length} reviewer(s):**\n`;
1367
- result.added.forEach((r) => {
1368
- md += `- ${r.displayName || r.id || 'Unknown'}${r.isRequired ? ' (Required)' : ''}\n`;
1369
- });
1370
- }
1371
- if (result.removed.length > 0) {
1372
- md += `\n**Removed ${result.removed.length} reviewer(s):**\n`;
1373
- result.removed.forEach((r) => {
1374
- md += `- ${r}\n`;
1375
- });
1376
- }
1377
- return (0, Common_1.formatMcpResponse)(result, md, false, true);
1378
- }
1379
- catch (error) {
1380
- return (0, Common_1.formatErrorResponse)(error);
1381
- }
1382
- }
1383
- /**
1384
- * Reply to an existing comment thread on a PR
1385
- */
1386
- async replyToComment(params) {
1387
- try {
1388
- const result = await this.gitService.replyToComment(params);
1389
- let md = `## Reply Added to Thread #${params.threadId}\n\n`;
1390
- md += `**Author:** ${result.author?.displayName || 'Unknown'}\n`;
1391
- md += `**Comment ID:** ${result.id}\n\n`;
1392
- md += `> ${params.comment}\n`;
1393
- return (0, Common_1.formatMcpResponse)(result, md, false, true);
1394
- }
1395
- catch (error) {
1396
- return (0, Common_1.formatErrorResponse)(error);
1397
- }
1398
- }
1399
- /**
1400
- * Update a comment thread's status (resolve/reactivate)
1401
- */
1402
- async updatePullRequestThread(params) {
1403
- try {
1404
- const result = await this.gitService.updatePullRequestThread(params);
1405
- let md = `## Thread #${params.threadId} Updated\n\n`;
1406
- md += `**Status:** ${params.status}\n`;
1407
- return (0, Common_1.formatMcpResponse)(result, md, false, true);
1408
- }
1409
- catch (error) {
1410
- return (0, Common_1.formatErrorResponse)(error);
1411
- }
1412
- }
1413
- /**
1414
- * Create a new branch from a source ref (branch name or commit SHA)
1415
- */
1416
- async createBranch(params) {
1417
- try {
1418
- const result = await this.gitService.createBranch(params);
1419
- let md = `## Branch Created\n\n`;
1420
- md += `| Property | Value |\n|---|---|\n`;
1421
- md += `| **Branch** | \`${result.name || params.branchName}\` |\n`;
1422
- md += `| **New Object ID** | \`${(0, formatHelpers_1.truncateText)(result.newObjectId || '-', 12)}\` |\n`;
1423
- md += `| **Success** | ${result.success !== false ? 'Yes' : 'No'} |\n`;
1424
- return (0, Common_1.formatMcpResponse)(result, md, false, true);
1425
- }
1426
- catch (error) {
1427
- return (0, Common_1.formatErrorResponse)(error);
1428
- }
1429
- }
1430
- }
1431
- exports.GitTools = GitTools;
1432
- exports.GitToolMethods = (0, getClassMethods_1.default)(GitTools.prototype);
1433
- // ── Helper Functions ─────────────────────────────────────────────
1434
- function getVoteLabel(vote) {
1435
- switch (vote) {
1436
- case 10: return '✅ Approved';
1437
- case 5: return '👍 Approved with Suggestions';
1438
- case 0: return '⏳ No Vote';
1439
- case -5: return '⏸️ Waiting for Author';
1440
- case -10: return '❌ Rejected';
1441
- default: return `${vote ?? 'N/A'}`;
1442
- }
1443
- }
1444
- function getMergeStatusLabel(status) {
1445
- switch (status) {
1446
- case 0: return 'Not Set';
1447
- case 1: return 'Queued';
1448
- case 2: return 'Conflicts';
1449
- case 3: return 'Succeeded';
1450
- case 4: return 'Rejected by Policy';
1451
- case 5: return 'Failure';
1452
- default: return `${status ?? 'N/A'}`;
1453
- }
1454
- }
1455
- function getMergeStrategyLabel(strategy) {
1456
- switch (strategy) {
1457
- case 1: return 'No Fast-Forward';
1458
- case 2: return 'Squash';
1459
- case 3: return 'Rebase';
1460
- case 4: return 'Rebase Merge';
1461
- default: return `${strategy ?? 'Default'}`;
1462
- }
1463
- }
1464
- function getPolicyStatusLabel(status) {
1465
- switch (status) {
1466
- case 0: return '⏳ Queued';
1467
- case 1: return '🔄 Running';
1468
- case 2: return '✅ Approved';
1469
- case 3: return '❌ Rejected';
1470
- case 4: return '➖ N/A';
1471
- case 5: return '⚠️ Broken';
1472
- default: return `Unknown (${status})`;
1473
- }
1474
- }
1475
- //# sourceMappingURL=GitTools.js.map