@achieveai/azuredevops-mcp 1.3.14 → 1.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Services/BuildService.project.test.js +91 -0
- package/dist/Services/BuildService.project.test.js.map +1 -0
- package/dist/Services/EntraAuthHandler.js +24 -0
- package/dist/Services/EntraAuthHandler.js.map +1 -1
- package/dist/Services/GitService.js +118 -83
- package/dist/Services/GitService.js.map +1 -1
- package/dist/Services/GitService.project.test.js +407 -0
- package/dist/Services/GitService.project.test.js.map +1 -0
- package/dist/Services/WorkItemService.js +2 -2
- package/dist/Services/WorkItemService.js.map +1 -1
- package/dist/index.js +45 -24
- package/dist/index.js.map +1 -1
- package/dist/utils/formatHelpers.js +19 -0
- package/dist/utils/formatHelpers.js.map +1 -1
- package/package.json +5 -3
|
@@ -20,10 +20,11 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
20
20
|
/**
|
|
21
21
|
* Get work item references linked to a pull request
|
|
22
22
|
*/
|
|
23
|
-
async getPullRequestWorkItemRefs(repository, pullRequestId) {
|
|
23
|
+
async getPullRequestWorkItemRefs(repository, pullRequestId, project) {
|
|
24
24
|
const gitApi = await this.getGitApi();
|
|
25
|
-
const
|
|
26
|
-
const
|
|
25
|
+
const effectiveProject = project || this.config.project;
|
|
26
|
+
const repositoryId = await this.resolveRepositoryId(repository, effectiveProject);
|
|
27
|
+
const refs = await gitApi.getPullRequestWorkItemRefs(repositoryId, pullRequestId, effectiveProject);
|
|
27
28
|
if (!refs || refs.length === 0)
|
|
28
29
|
return [];
|
|
29
30
|
return refs.map(ref => {
|
|
@@ -127,8 +128,9 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
127
128
|
async listBranches(params) {
|
|
128
129
|
try {
|
|
129
130
|
const gitApi = await this.getGitApi();
|
|
131
|
+
const project = params.project || this.config.project;
|
|
130
132
|
// Resolve repository name/ID to actual repository ID
|
|
131
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
133
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
132
134
|
const branches = await gitApi.getBranches(repositoryId, params.filter);
|
|
133
135
|
if (params.top && branches.length > params.top) {
|
|
134
136
|
return branches.slice(0, params.top);
|
|
@@ -147,8 +149,9 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
147
149
|
async searchCode(params) {
|
|
148
150
|
try {
|
|
149
151
|
const gitApi = await this.getGitApi();
|
|
152
|
+
const project = params.projectId || this.config.project;
|
|
150
153
|
// Resolve repository name/ID to actual repository ID if provided
|
|
151
|
-
const repositoryId = params.repository ? await this.resolveRepositoryId(params.repository) : "";
|
|
154
|
+
const repositoryId = params.repository ? await this.resolveRepositoryId(params.repository, project) : "";
|
|
152
155
|
// This is a simplified implementation using item search
|
|
153
156
|
// For more comprehensive code search, you'd use the Search API
|
|
154
157
|
const items = await gitApi.getItems(repositoryId || "", undefined, undefined, undefined, true, undefined, undefined, undefined, undefined, undefined);
|
|
@@ -177,8 +180,9 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
177
180
|
async browseRepository(params) {
|
|
178
181
|
try {
|
|
179
182
|
const gitApi = await this.getGitApi();
|
|
183
|
+
const project = params.project || this.config.project;
|
|
180
184
|
// Resolve repository name/ID to actual repository ID
|
|
181
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
185
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
182
186
|
const items = await gitApi.getItems(repositoryId, undefined, params.path, undefined, true, undefined, undefined, undefined, undefined, undefined);
|
|
183
187
|
return items;
|
|
184
188
|
}
|
|
@@ -193,8 +197,9 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
193
197
|
async getFileContent(params) {
|
|
194
198
|
try {
|
|
195
199
|
const gitApi = await this.getGitApi();
|
|
200
|
+
const project = params.project || this.config.project;
|
|
196
201
|
// Resolve repository name/ID to actual repository ID
|
|
197
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
202
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
198
203
|
// Get the file content as a stream
|
|
199
204
|
const content = await gitApi.getItemContent(repositoryId, params.path, undefined, undefined);
|
|
200
205
|
let fileContent = '';
|
|
@@ -266,11 +271,11 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
266
271
|
/**
|
|
267
272
|
* Get file content by object ID
|
|
268
273
|
*/
|
|
269
|
-
async getFileContentByObjectId(repositoryId, objectId) {
|
|
274
|
+
async getFileContentByObjectId(repositoryId, objectId, project) {
|
|
270
275
|
try {
|
|
271
276
|
const gitApi = await this.getGitApi();
|
|
272
277
|
// Get content by blob ID (object ID)
|
|
273
|
-
const content = await gitApi.getBlobContent(repositoryId, objectId, this.config.project);
|
|
278
|
+
const content = await gitApi.getBlobContent(repositoryId, objectId, project || this.config.project);
|
|
274
279
|
if (Buffer.isBuffer(content)) {
|
|
275
280
|
return content.toString('utf8');
|
|
276
281
|
}
|
|
@@ -311,11 +316,11 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
311
316
|
* Get the latest iteration number for a pull request
|
|
312
317
|
* Returns the most recent iteration that contains the latest changes
|
|
313
318
|
*/
|
|
314
|
-
async getLatestPullRequestIteration(repositoryId, pullRequestId) {
|
|
319
|
+
async getLatestPullRequestIteration(repositoryId, pullRequestId, project) {
|
|
315
320
|
try {
|
|
316
321
|
const gitApi = await this.getGitApi();
|
|
317
322
|
// Get all iterations for the pull request
|
|
318
|
-
const iterations = await gitApi.getPullRequestIterations(repositoryId, pullRequestId, this.config.project);
|
|
323
|
+
const iterations = await gitApi.getPullRequestIterations(repositoryId, pullRequestId, project || this.config.project);
|
|
319
324
|
if (!iterations || iterations.length === 0) {
|
|
320
325
|
// Fallback to iteration 1 if no iterations found
|
|
321
326
|
return 1;
|
|
@@ -538,8 +543,9 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
538
543
|
};
|
|
539
544
|
// Get commits with proper search criteria for richer data
|
|
540
545
|
// Resolve repository name/ID to actual repository ID
|
|
541
|
-
const
|
|
542
|
-
const
|
|
546
|
+
const project = params.projectId || this.config.project;
|
|
547
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
548
|
+
const commits = await gitApi.getCommits(repositoryId, searchCriteria, project);
|
|
543
549
|
// The commits are already filtered and paginated by the API
|
|
544
550
|
return commits || [];
|
|
545
551
|
}
|
|
@@ -577,8 +583,9 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
577
583
|
async getPullRequests(params) {
|
|
578
584
|
try {
|
|
579
585
|
const gitApi = await this.getGitApi();
|
|
586
|
+
const project = params.project || params.projectId || this.config.project;
|
|
580
587
|
// Resolve repository name/ID to actual repository ID
|
|
581
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
588
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
582
589
|
// Create search criteria with proper types
|
|
583
590
|
const searchCriteria = {
|
|
584
591
|
repositoryId: repositoryId,
|
|
@@ -599,7 +606,7 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
599
606
|
searchCriteria.status = 0;
|
|
600
607
|
// 'all' doesn't need to be set
|
|
601
608
|
}
|
|
602
|
-
const pullRequests = await this.withAuthRetry(() => gitApi.getPullRequests(repositoryId, searchCriteria,
|
|
609
|
+
const pullRequests = await this.withAuthRetry(() => gitApi.getPullRequests(repositoryId, searchCriteria, project, undefined, // maxCommentLength
|
|
603
610
|
params.skip || 0, params.top || 50));
|
|
604
611
|
// Note: Work item integration could be added here in the future
|
|
605
612
|
// For now, we return the basic pull request data with rich reviewer information
|
|
@@ -616,6 +623,7 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
616
623
|
async createPullRequest(params) {
|
|
617
624
|
try {
|
|
618
625
|
const gitApi = await this.getGitApi();
|
|
626
|
+
const project = params.project || this.config.project;
|
|
619
627
|
const pullRequest = {
|
|
620
628
|
sourceRefName: params.sourceRefName,
|
|
621
629
|
targetRefName: params.targetRefName,
|
|
@@ -624,8 +632,8 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
624
632
|
reviewers: params.reviewers ? params.reviewers.map(id => ({ id })) : undefined
|
|
625
633
|
};
|
|
626
634
|
// Resolve repository name/ID to actual repository ID
|
|
627
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
628
|
-
const createdPullRequest = await gitApi.createPullRequest(pullRequest, repositoryId,
|
|
635
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
636
|
+
const createdPullRequest = await gitApi.createPullRequest(pullRequest, repositoryId, project);
|
|
629
637
|
return createdPullRequest;
|
|
630
638
|
}
|
|
631
639
|
catch (error) {
|
|
@@ -639,16 +647,17 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
639
647
|
/**
|
|
640
648
|
* Get policy evaluations for a pull request
|
|
641
649
|
*/
|
|
642
|
-
async getPolicyEvaluations(repository, pullRequestId) {
|
|
650
|
+
async getPolicyEvaluations(repository, pullRequestId, projectOverride) {
|
|
643
651
|
try {
|
|
644
652
|
const policyApi = await this.connection.getPolicyApi();
|
|
653
|
+
const effectiveProject = projectOverride || this.config.project;
|
|
645
654
|
// Need project GUID for artifact ID
|
|
646
655
|
const coreApi = await this.connection.getCoreApi();
|
|
647
|
-
const project = await coreApi.getProject(
|
|
656
|
+
const project = await coreApi.getProject(effectiveProject);
|
|
648
657
|
const projectId = project.id;
|
|
649
658
|
// Artifact ID format uses %2F between segments (per MEMORY.md)
|
|
650
659
|
const artifactId = `vstfs:///CodeReview/CodeReviewId/${projectId}%2F${pullRequestId}`;
|
|
651
|
-
const evaluations = await policyApi.getPolicyEvaluations(
|
|
660
|
+
const evaluations = await policyApi.getPolicyEvaluations(effectiveProject, artifactId);
|
|
652
661
|
return evaluations || [];
|
|
653
662
|
}
|
|
654
663
|
catch (error) {
|
|
@@ -659,14 +668,15 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
659
668
|
async getPullRequest(params) {
|
|
660
669
|
try {
|
|
661
670
|
const gitApi = await this.getGitApi();
|
|
671
|
+
const project = params.project || this.config.project;
|
|
662
672
|
// Resolve repository name/ID to actual repository ID
|
|
663
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
664
|
-
const pullRequest = await this.withAuthRetry(() => gitApi.getPullRequest(repositoryId, params.pullRequestId,
|
|
673
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
674
|
+
const pullRequest = await this.withAuthRetry(() => gitApi.getPullRequest(repositoryId, params.pullRequestId, project));
|
|
665
675
|
// Create enhanced response with work items
|
|
666
676
|
const enhancedPullRequest = { ...pullRequest };
|
|
667
677
|
// Fetch associated work items
|
|
668
678
|
try {
|
|
669
|
-
const workItemRefs = await gitApi.getPullRequestWorkItemRefs(repositoryId, params.pullRequestId,
|
|
679
|
+
const workItemRefs = await gitApi.getPullRequestWorkItemRefs(repositoryId, params.pullRequestId, project);
|
|
670
680
|
if (workItemRefs && workItemRefs.length > 0) {
|
|
671
681
|
// Get work item IDs from references
|
|
672
682
|
const workItemIds = workItemRefs
|
|
@@ -679,7 +689,7 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
679
689
|
if (workItemIds.length > 0) {
|
|
680
690
|
// Fetch work item details
|
|
681
691
|
const witApi = await this.connection.getWorkItemTrackingApi();
|
|
682
|
-
const workItems = await witApi.getWorkItems(workItemIds, undefined, undefined, undefined, undefined,
|
|
692
|
+
const workItems = await witApi.getWorkItems(workItemIds, undefined, undefined, undefined, undefined, project);
|
|
683
693
|
// Add work items to pull request object
|
|
684
694
|
enhancedPullRequest.workItems = workItems.map(wi => ({
|
|
685
695
|
id: wi.id,
|
|
@@ -701,7 +711,7 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
701
711
|
const include = params.include;
|
|
702
712
|
if (!include || include.length === 0 || include.includes('policies')) {
|
|
703
713
|
try {
|
|
704
|
-
const evaluations = await this.getPolicyEvaluations(params.repository, params.pullRequestId);
|
|
714
|
+
const evaluations = await this.getPolicyEvaluations(params.repository, params.pullRequestId, project);
|
|
705
715
|
enhancedPullRequest.policyEvaluations = evaluations;
|
|
706
716
|
}
|
|
707
717
|
catch {
|
|
@@ -723,14 +733,15 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
723
733
|
async getPullRequestComments(params) {
|
|
724
734
|
try {
|
|
725
735
|
const gitApi = await this.getGitApi();
|
|
736
|
+
const project = params.project || this.config.project;
|
|
726
737
|
// Resolve repository name/ID to actual repository ID
|
|
727
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
738
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
728
739
|
if (params.threadId) {
|
|
729
|
-
const thread = await gitApi.getPullRequestThread(repositoryId, params.pullRequestId, params.threadId,
|
|
740
|
+
const thread = await gitApi.getPullRequestThread(repositoryId, params.pullRequestId, params.threadId, project);
|
|
730
741
|
return thread;
|
|
731
742
|
}
|
|
732
743
|
else {
|
|
733
|
-
let threads = await gitApi.getThreads(repositoryId, params.pullRequestId,
|
|
744
|
+
let threads = await gitApi.getThreads(repositoryId, params.pullRequestId, project);
|
|
734
745
|
// Apply status filter
|
|
735
746
|
if (params.status && threads) {
|
|
736
747
|
const statusMap = {
|
|
@@ -761,14 +772,15 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
761
772
|
async approvePullRequest(params) {
|
|
762
773
|
try {
|
|
763
774
|
const gitApi = await this.getGitApi();
|
|
775
|
+
const project = params.project || this.config.project;
|
|
764
776
|
// Resolve repository name/ID to actual repository ID
|
|
765
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
777
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
766
778
|
// Resolve current user's identity (the "me" shorthand isn't supported by all ADO configurations)
|
|
767
779
|
const reviewerId = await this.getAuthenticatedUserId();
|
|
768
780
|
const vote = {
|
|
769
781
|
vote: 10
|
|
770
782
|
};
|
|
771
|
-
const result = await gitApi.createPullRequestReviewer(vote, repositoryId, params.pullRequestId, reviewerId,
|
|
783
|
+
const result = await gitApi.createPullRequestReviewer(vote, repositoryId, params.pullRequestId, reviewerId, project);
|
|
772
784
|
return result;
|
|
773
785
|
}
|
|
774
786
|
catch (error) {
|
|
@@ -782,6 +794,7 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
782
794
|
async mergePullRequest(params) {
|
|
783
795
|
try {
|
|
784
796
|
const gitApi = await this.getGitApi();
|
|
797
|
+
const project = params.project || this.config.project;
|
|
785
798
|
// Convert string merge strategy to number
|
|
786
799
|
let mergeStrategy = 1; // Default to noFastForward
|
|
787
800
|
if (params.mergeStrategy === 'rebase')
|
|
@@ -790,13 +803,13 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
790
803
|
mergeStrategy = 3;
|
|
791
804
|
else if (params.mergeStrategy === 'squash')
|
|
792
805
|
mergeStrategy = 4;
|
|
793
|
-
let repositoryId = await this.resolveRepositoryId(params.repository);
|
|
806
|
+
let repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
794
807
|
const result = await gitApi.updatePullRequest({
|
|
795
808
|
status: 3, // 3 = completed in PullRequestStatus enum
|
|
796
809
|
completionOptions: {
|
|
797
810
|
mergeStrategy: mergeStrategy
|
|
798
811
|
}
|
|
799
|
-
}, repositoryId, params.pullRequestId,
|
|
812
|
+
}, repositoryId, params.pullRequestId, project);
|
|
800
813
|
return result;
|
|
801
814
|
}
|
|
802
815
|
catch (error) {
|
|
@@ -810,6 +823,7 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
810
823
|
async completePullRequest(params) {
|
|
811
824
|
try {
|
|
812
825
|
const gitApi = await this.getGitApi();
|
|
826
|
+
const project = params.project || params.projectId || this.config.project;
|
|
813
827
|
// Get the current pull request
|
|
814
828
|
const pullRequest = await gitApi.getPullRequestById(params.pullRequestId);
|
|
815
829
|
// Convert string merge strategy to number
|
|
@@ -820,7 +834,7 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
820
834
|
mergeStrategy = 3;
|
|
821
835
|
else if (params.mergeStrategy === 'squash')
|
|
822
836
|
mergeStrategy = 4;
|
|
823
|
-
let repositoryId = await this.resolveRepositoryId(params.repository);
|
|
837
|
+
let repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
824
838
|
// Update the pull request to completed status
|
|
825
839
|
const updatedPullRequest = await gitApi.updatePullRequest({
|
|
826
840
|
status: 3, // 3 = completed in PullRequestStatus enum
|
|
@@ -842,11 +856,12 @@ class GitService extends AzureDevOpsService_1.AzureDevOpsService {
|
|
|
842
856
|
async addPullRequestInlineComment(params) {
|
|
843
857
|
try {
|
|
844
858
|
const gitApi = await this.getGitApi();
|
|
845
|
-
|
|
859
|
+
const project = params.project || this.config.project;
|
|
860
|
+
let repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
846
861
|
// Get the latest iteration number
|
|
847
|
-
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId);
|
|
862
|
+
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId, project);
|
|
848
863
|
// Get the changes for the file to get the change tracking ID
|
|
849
|
-
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, latestIteration,
|
|
864
|
+
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, latestIteration, project);
|
|
850
865
|
// Helper function to normalize paths by removing leading slash
|
|
851
866
|
const normalizePath = (path) => {
|
|
852
867
|
return path.startsWith('/') ? path.substring(1) : path;
|
|
@@ -926,8 +941,11 @@ Note: You can only add inline comments to files that have been modified in the P
|
|
|
926
941
|
};
|
|
927
942
|
}
|
|
928
943
|
// Create a thread with proper context for the comment
|
|
929
|
-
|
|
930
|
-
|
|
944
|
+
// PR comments are markdown-native (client-side rendered, no server-side format param).
|
|
945
|
+
// Normalise literal \n escapes that LLMs sometimes double-escape in tool-call JSON.
|
|
946
|
+
const content = params.format === 'html'
|
|
947
|
+
? params.comment
|
|
948
|
+
: (0, formatHelpers_1.normalizeLiteralEscapes)((0, formatHelpers_1.unescapeHtmlEntities)(params.comment));
|
|
931
949
|
const thread = {
|
|
932
950
|
comments: [{
|
|
933
951
|
content,
|
|
@@ -945,7 +963,7 @@ Note: You can only add inline comments to files that have been modified in the P
|
|
|
945
963
|
}
|
|
946
964
|
};
|
|
947
965
|
// Create the thread (which includes the comment)
|
|
948
|
-
const result = await gitApi.createThread(thread, repositoryId, params.pullRequestId,
|
|
966
|
+
const result = await gitApi.createThread(thread, repositoryId, params.pullRequestId, project);
|
|
949
967
|
return result;
|
|
950
968
|
}
|
|
951
969
|
catch (error) {
|
|
@@ -989,9 +1007,12 @@ Original error: ${errorMessage}`);
|
|
|
989
1007
|
async addPullRequestFileComment(params) {
|
|
990
1008
|
try {
|
|
991
1009
|
const gitApi = await this.getGitApi();
|
|
1010
|
+
const project = params.project || this.config.project;
|
|
992
1011
|
// Create a thread with proper context for a file-level comment
|
|
993
|
-
|
|
994
|
-
const content = format === '
|
|
1012
|
+
// PR comments are markdown-native — normalise literal \n escapes from LLM tool calls.
|
|
1013
|
+
const content = params.format === 'html'
|
|
1014
|
+
? params.comment
|
|
1015
|
+
: (0, formatHelpers_1.normalizeLiteralEscapes)((0, formatHelpers_1.unescapeHtmlEntities)(params.comment));
|
|
995
1016
|
const thread = {
|
|
996
1017
|
comments: [{
|
|
997
1018
|
content,
|
|
@@ -1005,9 +1026,9 @@ Original error: ${errorMessage}`);
|
|
|
1005
1026
|
}
|
|
1006
1027
|
};
|
|
1007
1028
|
// Resolve repository name/ID to actual repository ID
|
|
1008
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
1029
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1009
1030
|
// Create the thread (which includes the comment)
|
|
1010
|
-
const result = await gitApi.createThread(thread, repositoryId, params.pullRequestId,
|
|
1031
|
+
const result = await gitApi.createThread(thread, repositoryId, params.pullRequestId, project);
|
|
1011
1032
|
return result;
|
|
1012
1033
|
}
|
|
1013
1034
|
catch (error) {
|
|
@@ -1021,9 +1042,12 @@ Original error: ${errorMessage}`);
|
|
|
1021
1042
|
async addPullRequestComment(params) {
|
|
1022
1043
|
try {
|
|
1023
1044
|
const gitApi = await this.getGitApi();
|
|
1045
|
+
const project = params.project || this.config.project;
|
|
1024
1046
|
// Create a thread for a general PR comment (no file context)
|
|
1025
|
-
|
|
1026
|
-
const content = format === '
|
|
1047
|
+
// PR comments are markdown-native — normalise literal \n escapes from LLM tool calls.
|
|
1048
|
+
const content = params.format === 'html'
|
|
1049
|
+
? params.comment
|
|
1050
|
+
: (0, formatHelpers_1.normalizeLiteralEscapes)((0, formatHelpers_1.unescapeHtmlEntities)(params.comment));
|
|
1027
1051
|
const thread = {
|
|
1028
1052
|
comments: [{
|
|
1029
1053
|
content,
|
|
@@ -1034,9 +1058,9 @@ Original error: ${errorMessage}`);
|
|
|
1034
1058
|
// No threadContext for general PR comments
|
|
1035
1059
|
};
|
|
1036
1060
|
// Resolve repository name/ID to actual repository ID
|
|
1037
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
1061
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1038
1062
|
// Create the thread (which includes the comment)
|
|
1039
|
-
const result = await gitApi.createThread(thread, repositoryId, params.pullRequestId,
|
|
1063
|
+
const result = await gitApi.createThread(thread, repositoryId, params.pullRequestId, project);
|
|
1040
1064
|
return result;
|
|
1041
1065
|
}
|
|
1042
1066
|
catch (error) {
|
|
@@ -1050,16 +1074,17 @@ Original error: ${errorMessage}`);
|
|
|
1050
1074
|
async getPullRequestFileChanges(params) {
|
|
1051
1075
|
try {
|
|
1052
1076
|
const gitApi = await this.getGitApi();
|
|
1077
|
+
const project = params.project || this.config.project;
|
|
1053
1078
|
// Resolve repository name/ID to actual repository ID
|
|
1054
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
1079
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1055
1080
|
// If path is provided, we need to get the changes for that specific file
|
|
1056
1081
|
if (params.path) {
|
|
1057
1082
|
// Get the latest iteration number
|
|
1058
|
-
const iterationNumber = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId);
|
|
1083
|
+
const iterationNumber = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId, project);
|
|
1059
1084
|
let changes = null;
|
|
1060
1085
|
try {
|
|
1061
1086
|
// Get the changes from the latest iteration
|
|
1062
|
-
changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, iterationNumber,
|
|
1087
|
+
changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, iterationNumber, project);
|
|
1063
1088
|
}
|
|
1064
1089
|
catch (error) {
|
|
1065
1090
|
console.error('Error getting PR changes:', error);
|
|
@@ -1090,8 +1115,8 @@ Original error: ${errorMessage}`);
|
|
|
1090
1115
|
// Modified file - get both versions and calculate diff
|
|
1091
1116
|
try {
|
|
1092
1117
|
const [originalContent, currentContent] = await Promise.all([
|
|
1093
|
-
this.getFileContentByObjectId(repositoryId, change.item.originalObjectId),
|
|
1094
|
-
this.getFileContentByObjectId(repositoryId, change.item.objectId)
|
|
1118
|
+
this.getFileContentByObjectId(repositoryId, change.item.originalObjectId, project),
|
|
1119
|
+
this.getFileContentByObjectId(repositoryId, change.item.objectId, project)
|
|
1095
1120
|
]);
|
|
1096
1121
|
diffContent = this.calculateUnifiedDiff(originalContent, currentContent, change.item.path);
|
|
1097
1122
|
diffContent = this.addInlineCommentGuidance(diffContent, change.changeType);
|
|
@@ -1104,7 +1129,7 @@ Original error: ${errorMessage}`);
|
|
|
1104
1129
|
else if (change.changeType === 1 && change.item?.objectId) {
|
|
1105
1130
|
// Added file - get new content and format as all additions
|
|
1106
1131
|
try {
|
|
1107
|
-
const newContent = await this.getFileContentByObjectId(repositoryId, change.item.objectId);
|
|
1132
|
+
const newContent = await this.getFileContentByObjectId(repositoryId, change.item.objectId, project);
|
|
1108
1133
|
diffContent = this.calculateAddedFileDiff(newContent, change.item.path);
|
|
1109
1134
|
}
|
|
1110
1135
|
catch (error) {
|
|
@@ -1115,7 +1140,7 @@ Original error: ${errorMessage}`);
|
|
|
1115
1140
|
else if (change.changeType === 3 && change.item?.originalObjectId) {
|
|
1116
1141
|
// Deleted file - get original content and format as all deletions
|
|
1117
1142
|
try {
|
|
1118
|
-
const originalContent = await this.getFileContentByObjectId(repositoryId, change.item.originalObjectId);
|
|
1143
|
+
const originalContent = await this.getFileContentByObjectId(repositoryId, change.item.originalObjectId, project);
|
|
1119
1144
|
diffContent = this.calculateDeletedFileDiff(originalContent, change.item.path);
|
|
1120
1145
|
}
|
|
1121
1146
|
catch (error) {
|
|
@@ -1135,8 +1160,8 @@ Original error: ${errorMessage}`);
|
|
|
1135
1160
|
}
|
|
1136
1161
|
// If no path is provided, get all changes with diffs
|
|
1137
1162
|
// Get the latest iteration number
|
|
1138
|
-
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId);
|
|
1139
|
-
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, latestIteration,
|
|
1163
|
+
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId, project);
|
|
1164
|
+
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, latestIteration, project);
|
|
1140
1165
|
// Enhance with diff content for each change (smart selection to show variety)
|
|
1141
1166
|
const allChanges = changes.changeEntries || [];
|
|
1142
1167
|
// Smart selection: try to get examples of different change types
|
|
@@ -1161,8 +1186,8 @@ Original error: ${errorMessage}`);
|
|
|
1161
1186
|
// Modified file - get both versions and calculate diff
|
|
1162
1187
|
try {
|
|
1163
1188
|
const [originalContent, currentContent] = await Promise.all([
|
|
1164
|
-
this.getFileContentByObjectId(repositoryId, change.item.originalObjectId),
|
|
1165
|
-
this.getFileContentByObjectId(repositoryId, change.item.objectId)
|
|
1189
|
+
this.getFileContentByObjectId(repositoryId, change.item.originalObjectId, project),
|
|
1190
|
+
this.getFileContentByObjectId(repositoryId, change.item.objectId, project)
|
|
1166
1191
|
]);
|
|
1167
1192
|
diffContent = this.calculateUnifiedDiff(originalContent, currentContent, change.item.path);
|
|
1168
1193
|
diffContent = this.addInlineCommentGuidance(diffContent, change.changeType);
|
|
@@ -1175,7 +1200,7 @@ Original error: ${errorMessage}`);
|
|
|
1175
1200
|
else if (change.changeType === 1 && change.item?.objectId) {
|
|
1176
1201
|
// Added file - get new content and format as all additions
|
|
1177
1202
|
try {
|
|
1178
|
-
const newContent = await this.getFileContentByObjectId(repositoryId, change.item.objectId);
|
|
1203
|
+
const newContent = await this.getFileContentByObjectId(repositoryId, change.item.objectId, project);
|
|
1179
1204
|
diffContent = this.calculateAddedFileDiff(newContent, change.item.path);
|
|
1180
1205
|
}
|
|
1181
1206
|
catch (error) {
|
|
@@ -1186,7 +1211,7 @@ Original error: ${errorMessage}`);
|
|
|
1186
1211
|
else if (change.changeType === 3 && change.item?.originalObjectId) {
|
|
1187
1212
|
// Deleted file - get original content and format as all deletions
|
|
1188
1213
|
try {
|
|
1189
|
-
const originalContent = await this.getFileContentByObjectId(repositoryId, change.item.originalObjectId);
|
|
1214
|
+
const originalContent = await this.getFileContentByObjectId(repositoryId, change.item.originalObjectId, project);
|
|
1190
1215
|
diffContent = this.calculateDeletedFileDiff(originalContent, change.item.path);
|
|
1191
1216
|
}
|
|
1192
1217
|
catch (error) {
|
|
@@ -1217,11 +1242,12 @@ Original error: ${errorMessage}`);
|
|
|
1217
1242
|
async getPullRequestChangesCount(params) {
|
|
1218
1243
|
try {
|
|
1219
1244
|
const gitApi = await this.getGitApi();
|
|
1245
|
+
const project = params.project || this.config.project;
|
|
1220
1246
|
// Resolve repository name/ID to actual repository ID
|
|
1221
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
1247
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1222
1248
|
// Get the latest iteration number
|
|
1223
|
-
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId);
|
|
1224
|
-
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, latestIteration,
|
|
1249
|
+
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId, project);
|
|
1250
|
+
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, latestIteration, project);
|
|
1225
1251
|
return {
|
|
1226
1252
|
totalChanges: changes.changeEntries?.length || 0,
|
|
1227
1253
|
addedFiles: changes.changeEntries?.filter(entry => entry.changeType === GitInterfaces_1.VersionControlChangeType.Add).length || 0,
|
|
@@ -1240,11 +1266,12 @@ Original error: ${errorMessage}`);
|
|
|
1240
1266
|
async getAllPullRequestChanges(params) {
|
|
1241
1267
|
try {
|
|
1242
1268
|
const gitApi = await this.getGitApi();
|
|
1269
|
+
const project = params.project || this.config.project;
|
|
1243
1270
|
// Resolve repository name/ID to actual repository ID
|
|
1244
|
-
const repositoryId = await this.resolveRepositoryId(params.repository);
|
|
1271
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1245
1272
|
// Get the latest iteration number
|
|
1246
|
-
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId);
|
|
1247
|
-
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, latestIteration,
|
|
1273
|
+
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, params.pullRequestId, project);
|
|
1274
|
+
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, params.pullRequestId, latestIteration, project);
|
|
1248
1275
|
let changeEntries = changes.changeEntries || [];
|
|
1249
1276
|
// Apply pagination if specified
|
|
1250
1277
|
if (params.skip && params.skip > 0) {
|
|
@@ -1266,12 +1293,13 @@ Original error: ${errorMessage}`);
|
|
|
1266
1293
|
/**
|
|
1267
1294
|
* Get list of changed files in a pull request (useful for knowing which files can have inline comments)
|
|
1268
1295
|
*/
|
|
1269
|
-
async getPullRequestChangedFilesList(repositoryId, pullRequestId) {
|
|
1296
|
+
async getPullRequestChangedFilesList(repositoryId, pullRequestId, project) {
|
|
1270
1297
|
try {
|
|
1271
1298
|
const gitApi = await this.getGitApi();
|
|
1299
|
+
const effectiveProject = project || this.config.project;
|
|
1272
1300
|
// Get the latest iteration number
|
|
1273
|
-
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, pullRequestId);
|
|
1274
|
-
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, pullRequestId, latestIteration,
|
|
1301
|
+
const latestIteration = await this.getLatestPullRequestIteration(repositoryId, pullRequestId, effectiveProject);
|
|
1302
|
+
const changes = await gitApi.getPullRequestIterationChanges(repositoryId, pullRequestId, latestIteration, effectiveProject);
|
|
1275
1303
|
return changes.changeEntries?.map(entry => entry.item?.path).filter((path) => Boolean(path)) || [];
|
|
1276
1304
|
}
|
|
1277
1305
|
catch (error) {
|
|
@@ -1407,7 +1435,8 @@ Original error: ${errorMessage}`);
|
|
|
1407
1435
|
*/
|
|
1408
1436
|
async updatePullRequest(params) {
|
|
1409
1437
|
const gitApi = await this.getGitApi();
|
|
1410
|
-
const
|
|
1438
|
+
const project = params.project || this.config.project;
|
|
1439
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1411
1440
|
const updatePayload = {};
|
|
1412
1441
|
if (params.title !== undefined)
|
|
1413
1442
|
updatePayload.title = params.title;
|
|
@@ -1441,14 +1470,15 @@ Original error: ${errorMessage}`);
|
|
|
1441
1470
|
updatePayload.completionOptions = completionOptions;
|
|
1442
1471
|
}
|
|
1443
1472
|
}
|
|
1444
|
-
return await gitApi.updatePullRequest(updatePayload, repositoryId, params.pullRequestId,
|
|
1473
|
+
return await gitApi.updatePullRequest(updatePayload, repositoryId, params.pullRequestId, project);
|
|
1445
1474
|
}
|
|
1446
1475
|
/**
|
|
1447
1476
|
* Add or remove reviewers on a pull request.
|
|
1448
1477
|
*/
|
|
1449
1478
|
async updatePullRequestReviewers(params) {
|
|
1450
1479
|
const gitApi = await this.getGitApi();
|
|
1451
|
-
const
|
|
1480
|
+
const project = params.project || this.config.project;
|
|
1481
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1452
1482
|
const results = { added: [], removed: [] };
|
|
1453
1483
|
if (params.reviewersToAdd && params.reviewersToAdd.length > 0) {
|
|
1454
1484
|
for (const reviewer of params.reviewersToAdd) {
|
|
@@ -1456,13 +1486,13 @@ Original error: ${errorMessage}`);
|
|
|
1456
1486
|
id: reviewer,
|
|
1457
1487
|
isRequired: params.makeRequired || false,
|
|
1458
1488
|
};
|
|
1459
|
-
const result = await gitApi.createPullRequestReviewer(reviewerObj, repositoryId, params.pullRequestId, reviewer,
|
|
1489
|
+
const result = await gitApi.createPullRequestReviewer(reviewerObj, repositoryId, params.pullRequestId, reviewer, project);
|
|
1460
1490
|
results.added.push(result);
|
|
1461
1491
|
}
|
|
1462
1492
|
}
|
|
1463
1493
|
if (params.reviewersToRemove && params.reviewersToRemove.length > 0) {
|
|
1464
1494
|
for (const reviewer of params.reviewersToRemove) {
|
|
1465
|
-
await gitApi.deletePullRequestReviewer(repositoryId, params.pullRequestId, reviewer,
|
|
1495
|
+
await gitApi.deletePullRequestReviewer(repositoryId, params.pullRequestId, reviewer, project);
|
|
1466
1496
|
results.removed.push(reviewer);
|
|
1467
1497
|
}
|
|
1468
1498
|
}
|
|
@@ -1473,22 +1503,26 @@ Original error: ${errorMessage}`);
|
|
|
1473
1503
|
*/
|
|
1474
1504
|
async replyToComment(params) {
|
|
1475
1505
|
const gitApi = await this.getGitApi();
|
|
1476
|
-
const
|
|
1477
|
-
const
|
|
1478
|
-
|
|
1506
|
+
const project = params.project || this.config.project;
|
|
1507
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1508
|
+
// PR comments are markdown-native — normalise literal \n escapes from LLM tool calls.
|
|
1509
|
+
const commentContent = params.format === 'html'
|
|
1510
|
+
? params.comment
|
|
1511
|
+
: (0, formatHelpers_1.normalizeLiteralEscapes)((0, formatHelpers_1.unescapeHtmlEntities)(params.comment));
|
|
1479
1512
|
const comment = {
|
|
1480
1513
|
content: commentContent,
|
|
1481
1514
|
parentCommentId: 0, // 0 = reply to thread
|
|
1482
1515
|
commentType: 1, // text comment
|
|
1483
1516
|
};
|
|
1484
|
-
return await gitApi.createComment(comment, repositoryId, params.pullRequestId, params.threadId,
|
|
1517
|
+
return await gitApi.createComment(comment, repositoryId, params.pullRequestId, params.threadId, project);
|
|
1485
1518
|
}
|
|
1486
1519
|
/**
|
|
1487
1520
|
* Update a comment thread's status (resolve/reactivate).
|
|
1488
1521
|
*/
|
|
1489
1522
|
async updatePullRequestThread(params) {
|
|
1490
1523
|
const gitApi = await this.getGitApi();
|
|
1491
|
-
const
|
|
1524
|
+
const project = params.project || this.config.project;
|
|
1525
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1492
1526
|
const statusMap = {
|
|
1493
1527
|
'active': 1,
|
|
1494
1528
|
'fixed': 2,
|
|
@@ -1501,20 +1535,21 @@ Original error: ${errorMessage}`);
|
|
|
1501
1535
|
const threadUpdate = {
|
|
1502
1536
|
status: statusMap[params.status] ?? 1,
|
|
1503
1537
|
};
|
|
1504
|
-
return await gitApi.updateThread(threadUpdate, repositoryId, params.pullRequestId, params.threadId,
|
|
1538
|
+
return await gitApi.updateThread(threadUpdate, repositoryId, params.pullRequestId, params.threadId, project);
|
|
1505
1539
|
}
|
|
1506
1540
|
/**
|
|
1507
1541
|
* Create a new branch from a source ref.
|
|
1508
1542
|
*/
|
|
1509
1543
|
async createBranch(params) {
|
|
1510
1544
|
const gitApi = await this.getGitApi();
|
|
1511
|
-
const
|
|
1545
|
+
const project = params.project || this.config.project;
|
|
1546
|
+
const repositoryId = await this.resolveRepositoryId(params.repository, project);
|
|
1512
1547
|
// Resolve source ref to a commit ID
|
|
1513
1548
|
let sourceObjectId = params.sourceRef;
|
|
1514
1549
|
// If sourceRef looks like a branch name, resolve it to commit ID
|
|
1515
1550
|
if (!sourceObjectId.match(/^[0-9a-f]{40}$/i)) {
|
|
1516
1551
|
const branchName = sourceObjectId.replace(/^refs\/heads\//, '');
|
|
1517
|
-
const branches = await gitApi.getBranches(repositoryId,
|
|
1552
|
+
const branches = await gitApi.getBranches(repositoryId, project);
|
|
1518
1553
|
const branch = branches?.find((b) => b.name === branchName || b.name === `refs/heads/${branchName}`);
|
|
1519
1554
|
if (!branch || !branch.commit?.commitId) {
|
|
1520
1555
|
throw new Error(`Source branch '${branchName}' not found or has no commits.`);
|
|
@@ -1529,7 +1564,7 @@ Original error: ${errorMessage}`);
|
|
|
1529
1564
|
oldObjectId: '0000000000000000000000000000000000000000',
|
|
1530
1565
|
newObjectId: sourceObjectId,
|
|
1531
1566
|
}];
|
|
1532
|
-
const result = await gitApi.updateRefs(refUpdate, repositoryId,
|
|
1567
|
+
const result = await gitApi.updateRefs(refUpdate, repositoryId, project);
|
|
1533
1568
|
return result?.[0];
|
|
1534
1569
|
}
|
|
1535
1570
|
}
|