@achieveai/azuredevops-mcp 1.3.17 → 1.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/Interfaces/Common.js +37 -1
- package/dist/Interfaces/Common.js.map +1 -1
- package/dist/Services/AzureDevOpsService.js +234 -32
- package/dist/Services/AzureDevOpsService.js.map +1 -1
- package/dist/Services/BoardsSprintsService.js +111 -13
- package/dist/Services/BoardsSprintsService.js.map +1 -1
- package/dist/Services/BuildService.js +157 -24
- package/dist/Services/BuildService.js.map +1 -1
- package/dist/Services/GitService.js +26 -3
- package/dist/Services/GitService.js.map +1 -1
- package/dist/Services/ProjectService.js +47 -6
- package/dist/Services/ProjectService.js.map +1 -1
- package/dist/Services/WorkItemService.js +183 -170
- package/dist/Services/WorkItemService.js.map +1 -1
- package/dist/Tools/BoardsSprintsTools.js +2 -8
- package/dist/Tools/BoardsSprintsTools.js.map +1 -1
- package/dist/Tools/BuildTools.js +5 -8
- package/dist/Tools/BuildTools.js.map +1 -1
- package/dist/Tools/GitTools.js +177 -62
- package/dist/Tools/GitTools.js.map +1 -1
- package/dist/Tools/WorkItemTools.js +110 -172
- package/dist/Tools/WorkItemTools.js.map +1 -1
- package/dist/index.js +31 -26
- package/dist/index.js.map +1 -1
- package/dist/utils/apiUsageGuidance.js +336 -0
- package/dist/utils/apiUsageGuidance.js.map +1 -0
- package/dist/utils/formatHelpers.js +15 -0
- package/dist/utils/formatHelpers.js.map +1 -1
- package/package.json +3 -3
- package/dist/Services/BuildService.project.test.js +0 -91
- package/dist/Services/BuildService.project.test.js.map +0 -1
- package/dist/Services/GitService.project.test.js +0 -407
- package/dist/Services/GitService.project.test.js.map +0 -1
- package/dist/package.json +0 -59
- package/dist/src/Interfaces/AIAssisted.js +0 -3
- package/dist/src/Interfaces/AIAssisted.js.map +0 -1
- package/dist/src/Interfaces/ArtifactManagement.js +0 -3
- package/dist/src/Interfaces/ArtifactManagement.js.map +0 -1
- package/dist/src/Interfaces/AzureDevOps.js +0 -3
- package/dist/src/Interfaces/AzureDevOps.js.map +0 -1
- package/dist/src/Interfaces/BoardsAndSprints.js +0 -3
- package/dist/src/Interfaces/BoardsAndSprints.js.map +0 -1
- package/dist/src/Interfaces/CodeAndRepositories.js +0 -3
- package/dist/src/Interfaces/CodeAndRepositories.js.map +0 -1
- package/dist/src/Interfaces/Common.js +0 -134
- package/dist/src/Interfaces/Common.js.map +0 -1
- package/dist/src/Interfaces/CostResourceManagement.js +0 -3
- package/dist/src/Interfaces/CostResourceManagement.js.map +0 -1
- package/dist/src/Interfaces/DevSecOps.js +0 -3
- package/dist/src/Interfaces/DevSecOps.js.map +0 -1
- package/dist/src/Interfaces/ExternalIntegrations.js +0 -3
- package/dist/src/Interfaces/ExternalIntegrations.js.map +0 -1
- package/dist/src/Interfaces/HybridCrossPlatform.js +0 -3
- package/dist/src/Interfaces/HybridCrossPlatform.js.map +0 -1
- package/dist/src/Interfaces/Pipelines.js +0 -3
- package/dist/src/Interfaces/Pipelines.js.map +0 -1
- package/dist/src/Interfaces/ProjectManagement.js +0 -3
- package/dist/src/Interfaces/ProjectManagement.js.map +0 -1
- package/dist/src/Interfaces/TestingCapabilities.js +0 -3
- package/dist/src/Interfaces/TestingCapabilities.js.map +0 -1
- package/dist/src/Interfaces/Wiki.js +0 -3
- package/dist/src/Interfaces/Wiki.js.map +0 -1
- package/dist/src/Interfaces/WorkItems.js +0 -3
- package/dist/src/Interfaces/WorkItems.js.map +0 -1
- package/dist/src/Services/AIAssistedDevelopmentService.js +0 -195
- package/dist/src/Services/AIAssistedDevelopmentService.js.map +0 -1
- package/dist/src/Services/ArtifactManagementService.js +0 -346
- package/dist/src/Services/ArtifactManagementService.js.map +0 -1
- package/dist/src/Services/AzureDevOpsService.js +0 -385
- package/dist/src/Services/AzureDevOpsService.js.map +0 -1
- package/dist/src/Services/BoardsSprintsService.js +0 -339
- package/dist/src/Services/BoardsSprintsService.js.map +0 -1
- package/dist/src/Services/BuildService.js +0 -405
- package/dist/src/Services/BuildService.js.map +0 -1
- package/dist/src/Services/DevSecOpsService.js +0 -307
- package/dist/src/Services/DevSecOpsService.js.map +0 -1
- package/dist/src/Services/EntraAuthHandler.js +0 -337
- package/dist/src/Services/EntraAuthHandler.js.map +0 -1
- package/dist/src/Services/GitService.js +0 -1595
- package/dist/src/Services/GitService.js.map +0 -1
- package/dist/src/Services/ProjectService.js +0 -257
- package/dist/src/Services/ProjectService.js.map +0 -1
- package/dist/src/Services/TestingCapabilitiesService.js +0 -149
- package/dist/src/Services/TestingCapabilitiesService.js.map +0 -1
- package/dist/src/Services/WikiService.js +0 -90
- package/dist/src/Services/WikiService.js.map +0 -1
- package/dist/src/Services/WorkItemService.js +0 -885
- package/dist/src/Services/WorkItemService.js.map +0 -1
- package/dist/src/Tools/AIAssistedDevelopmentTools.js +0 -137
- package/dist/src/Tools/AIAssistedDevelopmentTools.js.map +0 -1
- package/dist/src/Tools/ArtifactManagementTools.js +0 -140
- package/dist/src/Tools/ArtifactManagementTools.js.map +0 -1
- package/dist/src/Tools/BoardsSprintsTools.js +0 -338
- package/dist/src/Tools/BoardsSprintsTools.js.map +0 -1
- package/dist/src/Tools/BuildTools.js +0 -468
- package/dist/src/Tools/BuildTools.js.map +0 -1
- package/dist/src/Tools/DevSecOpsTools.js +0 -147
- package/dist/src/Tools/DevSecOpsTools.js.map +0 -1
- package/dist/src/Tools/GitTools.js +0 -1475
- package/dist/src/Tools/GitTools.js.map +0 -1
- package/dist/src/Tools/ProjectTools.js +0 -360
- package/dist/src/Tools/ProjectTools.js.map +0 -1
- package/dist/src/Tools/TestingCapabilitiesTools.js +0 -157
- package/dist/src/Tools/TestingCapabilitiesTools.js.map +0 -1
- package/dist/src/Tools/WikiTools.js +0 -137
- package/dist/src/Tools/WikiTools.js.map +0 -1
- package/dist/src/Tools/WorkItemTools.js +0 -862
- package/dist/src/Tools/WorkItemTools.js.map +0 -1
- package/dist/src/config.js +0 -176
- package/dist/src/config.js.map +0 -1
- package/dist/src/index.js +0 -1716
- package/dist/src/index.js.map +0 -1
- package/dist/src/utils/formatHelpers.js +0 -257
- package/dist/src/utils/formatHelpers.js.map +0 -1
- package/dist/src/utils/getClassMethods.js +0 -8
- package/dist/src/utils/getClassMethods.js.map +0 -1
- package/dist/src/utils/repositoryResolver.js +0 -40
- package/dist/src/utils/repositoryResolver.js.map +0 -1
|
@@ -1,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
|