@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.
@@ -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 repositoryId = await this.resolveRepositoryId(repository);
26
- const refs = await gitApi.getPullRequestWorkItemRefs(repositoryId, pullRequestId, this.config.project);
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 repositoryId = await this.resolveRepositoryId(params.repository);
542
- const commits = await gitApi.getCommits(repositoryId, searchCriteria, params.projectId || this.config.project);
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, this.config.project, undefined, // maxCommentLength
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, this.config.project);
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(this.config.project);
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(this.config.project, artifactId);
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, this.config.project));
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, this.config.project);
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, this.config.project);
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, this.config.project);
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, this.config.project);
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, this.config.project);
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, this.config.project);
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
- let repositoryId = await this.resolveRepositoryId(params.repository);
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, this.config.project);
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
- const format = params.format === 'html' ? 'html' : 'markdown';
930
- const content = format === 'markdown' ? (0, formatHelpers_1.unescapeHtmlEntities)(params.comment) : params.comment;
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, this.config.project);
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
- const format = params.format === 'html' ? 'html' : 'markdown';
994
- const content = format === 'markdown' ? (0, formatHelpers_1.unescapeHtmlEntities)(params.comment) : params.comment;
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, this.config.project);
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
- const format = params.format === 'html' ? 'html' : 'markdown';
1026
- const content = format === 'markdown' ? (0, formatHelpers_1.unescapeHtmlEntities)(params.comment) : params.comment;
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, this.config.project);
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, this.config.project);
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, this.config.project);
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, this.config.project);
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, this.config.project);
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, this.config.project);
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 repositoryId = await this.resolveRepositoryId(params.repository);
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, this.config.project);
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 repositoryId = await this.resolveRepositoryId(params.repository);
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, this.config.project);
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, this.config.project);
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 repositoryId = await this.resolveRepositoryId(params.repository);
1477
- const format = params.format === 'html' ? 'html' : 'markdown';
1478
- const commentContent = format === 'markdown' ? (0, formatHelpers_1.unescapeHtmlEntities)(params.comment) : params.comment;
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, this.config.project);
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 repositoryId = await this.resolveRepositoryId(params.repository);
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, this.config.project);
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 repositoryId = await this.resolveRepositoryId(params.repository);
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, this.config.project);
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, this.config.project);
1567
+ const result = await gitApi.updateRefs(refUpdate, repositoryId, project);
1533
1568
  return result?.[0];
1534
1569
  }
1535
1570
  }