@mcp-consultant-tools/azure-devops 26.0.0-beta.1 → 27.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/build/AzureDevOpsService.d.ts +179 -514
  2. package/build/AzureDevOpsService.d.ts.map +1 -1
  3. package/build/AzureDevOpsService.js +401 -994
  4. package/build/AzureDevOpsService.js.map +1 -1
  5. package/build/index.d.ts.map +1 -1
  6. package/build/index.js +1038 -844
  7. package/build/index.js.map +1 -1
  8. package/build/sync/file-utils.d.ts +86 -0
  9. package/build/sync/file-utils.d.ts.map +1 -0
  10. package/build/sync/file-utils.js +224 -0
  11. package/build/sync/file-utils.js.map +1 -0
  12. package/build/sync/git-utils.d.ts +31 -0
  13. package/build/sync/git-utils.d.ts.map +1 -0
  14. package/build/sync/git-utils.js +116 -0
  15. package/build/sync/git-utils.js.map +1 -0
  16. package/build/sync/html-converter.d.ts +32 -0
  17. package/build/sync/html-converter.d.ts.map +1 -0
  18. package/build/sync/html-converter.js +91 -0
  19. package/build/sync/html-converter.js.map +1 -0
  20. package/build/sync/html-detection.d.ts +93 -0
  21. package/build/sync/html-detection.d.ts.map +1 -0
  22. package/build/sync/html-detection.js +169 -0
  23. package/build/sync/html-detection.js.map +1 -0
  24. package/build/sync/index.d.ts +12 -0
  25. package/build/sync/index.d.ts.map +1 -0
  26. package/build/sync/index.js +12 -0
  27. package/build/sync/index.js.map +1 -0
  28. package/build/sync/markdown-serializer.d.ts +136 -0
  29. package/build/sync/markdown-serializer.d.ts.map +1 -0
  30. package/build/sync/markdown-serializer.js +646 -0
  31. package/build/sync/markdown-serializer.js.map +1 -0
  32. package/build/sync/task-serializer.d.ts +93 -0
  33. package/build/sync/task-serializer.d.ts.map +1 -0
  34. package/build/sync/task-serializer.js +395 -0
  35. package/build/sync/task-serializer.js.map +1 -0
  36. package/build/tool-examples.d.ts +56 -0
  37. package/build/tool-examples.d.ts.map +1 -0
  38. package/build/tool-examples.js +142 -0
  39. package/build/tool-examples.js.map +1 -0
  40. package/package.json +5 -2
@@ -1,5 +1,6 @@
1
1
  import axios from 'axios';
2
2
  import { marked } from 'marked';
3
+ import { getAllLargeTextFields } from './sync/html-detection.js';
3
4
  export class AzureDevOpsService {
4
5
  config;
5
6
  baseUrl;
@@ -13,20 +14,7 @@ export class AzureDevOpsService {
13
14
  enableWorkItemWrite: config.enableWorkItemWrite ?? false,
14
15
  enableWorkItemDelete: config.enableWorkItemDelete ?? false,
15
16
  enableWikiWrite: config.enableWikiWrite ?? false,
16
- // Tier 1: Read-only (master switch)
17
- showDevOpsAdminReadonly: config.showDevOpsAdminReadonly ?? false,
18
- // Tier 2: Upsert (create + update)
19
- enablePipelineUpsert: config.enablePipelineUpsert ?? false,
20
- enableServiceConnUpsert: config.enableServiceConnUpsert ?? false,
21
- enableVariableGroupUpsert: config.enableVariableGroupUpsert ?? false,
22
- enableAgentPoolUpsert: config.enableAgentPoolUpsert ?? false,
23
- enableEnvironmentUpsert: config.enableEnvironmentUpsert ?? false,
24
- // Tier 3: Delete/Disable (destructive operations)
25
- enablePipelineDelete: config.enablePipelineDelete ?? false,
26
- enableServiceConnDelete: config.enableServiceConnDelete ?? false,
27
- enableVariableGroupDelete: config.enableVariableGroupDelete ?? false,
28
- enableAgentPoolDisable: config.enableAgentPoolDisable ?? false,
29
- enableEnvironmentDelete: config.enableEnvironmentDelete ?? false,
17
+ enablePullRequestWrite: config.enablePullRequestWrite ?? false,
30
18
  };
31
19
  this.baseUrl = `https://dev.azure.com/${this.config.organization}`;
32
20
  this.searchUrl = `https://almsearch.dev.azure.com/${this.config.organization}`;
@@ -527,8 +515,8 @@ export class AzureDevOpsService {
527
515
  this.validateProject(project);
528
516
  // Comments API requires -preview suffix (not GA in 7.1)
529
517
  const response = await this.makeRequest(`${project}/_apis/wit/workItems/${workItemId}/comments?api-version=7.1-preview`);
530
- // Handle case where response.value may be undefined or empty
531
- const comments = response.value || [];
518
+ // v7.1 Comments API returns array in response.comments, not response.value
519
+ const comments = response.comments || response.value || [];
532
520
  return {
533
521
  workItemId,
534
522
  project,
@@ -595,6 +583,25 @@ export class AzureDevOpsService {
595
583
  project
596
584
  };
597
585
  }
586
+ /**
587
+ * Set work item field(s) to markdown format.
588
+ * This is IRREVERSIBLE - once set to markdown, cannot revert to HTML.
589
+ *
590
+ * @param project - Project name
591
+ * @param workItemId - Work item ID
592
+ * @param fields - Array of field names to set to markdown format (e.g., ['System.Description'])
593
+ */
594
+ async setFieldsToMarkdownFormat(project, workItemId, fields) {
595
+ if (!this.config.enableWorkItemWrite) {
596
+ throw new Error('Work item write operations are disabled. Set AZUREDEVOPS_ENABLE_WORK_ITEM_WRITE=true to enable.');
597
+ }
598
+ const patchOperations = fields.map(field => ({
599
+ op: 'add',
600
+ path: `/multilineFieldsFormat/${field}`,
601
+ value: 'Markdown'
602
+ }));
603
+ await this.updateWorkItem(project, workItemId, patchOperations);
604
+ }
598
605
  /**
599
606
  * Create a new work item
600
607
  * @param project The project name
@@ -619,6 +626,19 @@ export class AzureDevOpsService {
619
626
  value: fields[field]
620
627
  });
621
628
  });
629
+ // Auto-set markdown format for large text fields that are being set
630
+ // This ensures all new work items use markdown format (irreversible)
631
+ // Uses getAllLargeTextFields() to include custom fields like Custom.Howtotest
632
+ const allLargeTextFields = getAllLargeTextFields();
633
+ for (const field of allLargeTextFields) {
634
+ if (fields[field] !== undefined) {
635
+ patchOperations.push({
636
+ op: 'add',
637
+ path: `/multilineFieldsFormat/${field}`,
638
+ value: 'Markdown'
639
+ });
640
+ }
641
+ }
622
642
  // Handle parentId parameter (simplified parent relationship)
623
643
  if (parentId !== undefined) {
624
644
  const parentUrl = `${this.baseUrl}/${encodeURIComponent(project)}/_apis/wit/workItems/${parentId}`;
@@ -739,167 +759,17 @@ export class AzureDevOpsService {
739
759
  };
740
760
  }
741
761
  // ═══════════════════════════════════════════════════════════════════════════════
742
- // PIPELINE/BUILD OPERATIONS (DevOps Admin Tools)
762
+ // BUILD TROUBLESHOOTING OPERATIONS (Read-only)
763
+ // NOTE: These methods are duplicated in azure-devops-admin package.
764
+ // If you update these, also update packages/azure-devops-admin/src/AzureDevOpsAdminService.ts
743
765
  // ═══════════════════════════════════════════════════════════════════════════════
744
766
  /**
745
- * List all pipeline definitions in a project
746
- * @param project The project name
747
- * @returns List of pipeline definitions
748
- */
749
- async listPipelineDefinitions(project) {
750
- this.validateProject(project);
751
- const response = await this.makeRequest(`${project}/_apis/build/definitions?api-version=${this.apiVersion}`);
752
- return {
753
- project,
754
- totalCount: response.value.length,
755
- pipelines: response.value.map((def) => ({
756
- id: def.id,
757
- name: def.name,
758
- path: def.path,
759
- revision: def.revision,
760
- type: def.type,
761
- queueStatus: def.queueStatus,
762
- createdDate: def.createdDate,
763
- authoredBy: def.authoredBy?.displayName,
764
- repository: def.repository ? {
765
- id: def.repository.id,
766
- name: def.repository.name,
767
- type: def.repository.type
768
- } : null,
769
- process: def.process ? {
770
- type: def.process.type,
771
- yamlFilename: def.process.yamlFilename
772
- } : null,
773
- url: def._links?.web?.href
774
- }))
775
- };
776
- }
777
- /**
778
- * Get a specific pipeline definition with full details
779
- * @param project The project name
780
- * @param definitionId The pipeline definition ID
781
- * @returns Full pipeline definition
782
- */
783
- async getPipelineDefinition(project, definitionId) {
784
- this.validateProject(project);
785
- const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`);
786
- return {
787
- id: response.id,
788
- name: response.name,
789
- path: response.path,
790
- revision: response.revision,
791
- type: response.type,
792
- queueStatus: response.queueStatus,
793
- quality: response.quality,
794
- createdDate: response.createdDate,
795
- authoredBy: response.authoredBy?.displayName,
796
- project: response.project?.name,
797
- repository: response.repository ? {
798
- id: response.repository.id,
799
- name: response.repository.name,
800
- type: response.repository.type,
801
- defaultBranch: response.repository.defaultBranch,
802
- url: response.repository.url
803
- } : null,
804
- process: response.process ? {
805
- type: response.process.type,
806
- yamlFilename: response.process.yamlFilename
807
- } : null,
808
- triggers: response.triggers || [],
809
- variables: response.variables ? Object.keys(response.variables).reduce((acc, key) => {
810
- const variable = response.variables[key];
811
- acc[key] = {
812
- value: variable.isSecret ? '***SECRET***' : variable.value,
813
- isSecret: variable.isSecret || false,
814
- allowOverride: variable.allowOverride || false
815
- };
816
- return acc;
817
- }, {}) : {},
818
- queue: response.queue ? {
819
- id: response.queue.id,
820
- name: response.queue.name,
821
- pool: response.queue.pool?.name
822
- } : null,
823
- url: response._links?.web?.href
824
- };
825
- }
826
- /**
827
- * Get the YAML content for a pipeline definition
828
- * @param project The project name
829
- * @param definitionId The pipeline definition ID
830
- * @returns YAML content or location info for external repos
831
- */
832
- async getPipelineYaml(project, definitionId) {
833
- this.validateProject(project);
834
- // First get the pipeline definition to check repo type
835
- const definition = await this.getPipelineDefinition(project, definitionId);
836
- // If external repo (GitHub, Bitbucket, etc.), return helpful info instead of failing
837
- if (definition.repository?.type && definition.repository.type !== 'TfsGit') {
838
- return {
839
- definitionId,
840
- project,
841
- pipelineName: definition.name,
842
- yamlLocation: 'external',
843
- repositoryType: definition.repository.type,
844
- repositoryName: definition.repository.name,
845
- repositoryUrl: definition.repository.url,
846
- defaultBranch: definition.repository.defaultBranch,
847
- yamlFilename: definition.process?.yamlFilename,
848
- message: `YAML is stored in external ${definition.repository.type} repository. ` +
849
- `To view the YAML content, access the file "${definition.process?.yamlFilename}" ` +
850
- `at ${definition.repository.url}`
851
- };
852
- }
853
- // For Azure Repos (TfsGit), fetch YAML content directly
854
- const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}/yaml?api-version=${this.apiVersion}`);
855
- return {
856
- definitionId,
857
- project,
858
- pipelineName: definition.name,
859
- yamlLocation: 'azureRepos',
860
- yaml: response.yaml || response
861
- };
862
- }
863
- /**
864
- * List recent pipeline runs for a definition
865
- * @param project The project name
866
- * @param definitionId The pipeline definition ID
867
- * @param top Maximum number of results (default: 10)
868
- * @returns List of builds/runs
869
- */
870
- async listPipelineRuns(project, definitionId, top = 10) {
871
- this.validateProject(project);
872
- const response = await this.makeRequest(`${project}/_apis/build/builds?definitions=${definitionId}&$top=${top}&api-version=${this.apiVersion}`);
873
- return {
874
- project,
875
- definitionId,
876
- totalCount: response.value.length,
877
- builds: response.value.map((build) => ({
878
- id: build.id,
879
- buildNumber: build.buildNumber,
880
- status: build.status,
881
- result: build.result,
882
- queueTime: build.queueTime,
883
- startTime: build.startTime,
884
- finishTime: build.finishTime,
885
- sourceBranch: build.sourceBranch,
886
- sourceVersion: build.sourceVersion,
887
- requestedBy: build.requestedBy?.displayName,
888
- requestedFor: build.requestedFor?.displayName,
889
- reason: build.reason,
890
- priority: build.priority,
891
- url: build._links?.web?.href
892
- }))
893
- };
894
- }
895
- /**
896
- * Get build status and details
767
+ * Get build status with optional timeline and logs
897
768
  * @param project The project name
898
769
  * @param buildId The build ID
899
- * @param detail Level of detail: "summary" | "timeline" | "full"
900
- * @param timelineScope Timeline scope: 'stages' | 'jobs' | 'all' | 'problems' (default: 'problems')
901
- * @param maxIssues Maximum issues per timeline record (default: 5)
902
- * @returns Build details
770
+ * @param detail Level of detail: 'summary', 'timeline', or 'full'
771
+ * @param timelineScope Scope for timeline: 'stages', 'jobs', 'all', 'problems'
772
+ * @param maxIssues Maximum issues per record
903
773
  */
904
774
  async getBuildStatus(project, buildId, detail = 'summary', timelineScope = 'problems', maxIssues = 5) {
905
775
  this.validateProject(project);
@@ -925,12 +795,10 @@ export class AzureDevOpsService {
925
795
  project: response.project?.name,
926
796
  url: response._links?.web?.href
927
797
  };
928
- // Add timeline if requested
929
798
  if (detail === 'timeline' || detail === 'full') {
930
799
  const timeline = await this.getBuildTimeline(project, buildId, timelineScope, maxIssues);
931
800
  result.timeline = timeline;
932
801
  }
933
- // Add logs if full detail requested
934
802
  if (detail === 'full') {
935
803
  const logs = await this.getBuildLogs(project, buildId);
936
804
  result.logs = logs;
@@ -938,42 +806,35 @@ export class AzureDevOpsService {
938
806
  return result;
939
807
  }
940
808
  /**
941
- * Get build timeline (step-by-step breakdown)
809
+ * Get build timeline with step-by-step breakdown
942
810
  * @param project The project name
943
811
  * @param buildId The build ID
944
- * @param scope Filter scope: 'stages' | 'jobs' | 'all' | 'problems' (default: 'problems')
945
- * @param maxIssues Maximum issues per record (default: 5, prioritizes errors over warnings)
946
- * @returns Build timeline with records and summary stats
812
+ * @param scope Filter scope: 'stages', 'jobs', 'all', 'problems'
813
+ * @param maxIssues Maximum issues per record
947
814
  */
948
815
  async getBuildTimeline(project, buildId, scope = 'problems', maxIssues = 5) {
949
816
  this.validateProject(project);
950
817
  const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/timeline?api-version=${this.apiVersion}`);
951
818
  const allRecords = response.records || [];
952
- // Calculate summary stats from all records
953
819
  const summary = {
954
820
  total: allRecords.length,
955
821
  byType: {},
956
822
  byResult: {},
957
823
  totalErrors: 0,
958
824
  totalWarnings: 0,
959
- failed: [], // Names of failed items
825
+ failed: [],
960
826
  };
961
827
  for (const record of allRecords) {
962
- // Count by type
963
828
  summary.byType[record.type] = (summary.byType[record.type] || 0) + 1;
964
- // Count by result
965
829
  if (record.result) {
966
830
  summary.byResult[record.result] = (summary.byResult[record.result] || 0) + 1;
967
831
  }
968
- // Sum errors and warnings
969
832
  summary.totalErrors += record.errorCount || 0;
970
833
  summary.totalWarnings += record.warningCount || 0;
971
- // Track failed item names
972
834
  if (record.result === 'failed' || record.result === 'canceled') {
973
835
  summary.failed.push(`${record.type}: ${record.name}`);
974
836
  }
975
837
  }
976
- // Filter records based on scope
977
838
  let filteredRecords = allRecords;
978
839
  switch (scope) {
979
840
  case 'stages':
@@ -990,14 +851,11 @@ export class AzureDevOpsService {
990
851
  break;
991
852
  case 'all':
992
853
  default:
993
- // Keep all records
994
854
  break;
995
855
  }
996
- // Map and truncate issues (prioritizing errors over warnings)
997
856
  const truncateIssues = (issues, max) => {
998
857
  if (!issues || issues.length === 0)
999
858
  return { items: [], totalCount: 0, truncated: false };
1000
- // Sort: errors first, then warnings, then others
1001
859
  const sorted = [...issues].sort((a, b) => {
1002
860
  const priority = (issue) => {
1003
861
  if (issue.type === 'error')
@@ -1028,7 +886,7 @@ export class AzureDevOpsService {
1028
886
  order: record.order,
1029
887
  errorCount: record.errorCount,
1030
888
  warningCount: record.warningCount,
1031
- log: record.log ? { id: record.log.id } : null, // Removed URL to save space
889
+ log: record.log ? { id: record.log.id } : null,
1032
890
  issues: truncatedIssues.items,
1033
891
  issuesTruncated: truncatedIssues.truncated,
1034
892
  totalIssueCount: truncatedIssues.totalCount
@@ -1044,16 +902,14 @@ export class AzureDevOpsService {
1044
902
  };
1045
903
  }
1046
904
  /**
1047
- * Get build logs
905
+ * Get build logs (list or specific log content)
1048
906
  * @param project The project name
1049
907
  * @param buildId The build ID
1050
- * @param logId Optional specific log ID
1051
- * @returns Build logs
908
+ * @param logId Optional specific log ID to retrieve content
1052
909
  */
1053
910
  async getBuildLogs(project, buildId, logId) {
1054
911
  this.validateProject(project);
1055
912
  if (logId !== undefined) {
1056
- // Get specific log content
1057
913
  const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs/${logId}?api-version=${this.apiVersion}`);
1058
914
  return {
1059
915
  buildId,
@@ -1062,7 +918,6 @@ export class AzureDevOpsService {
1062
918
  content: response
1063
919
  };
1064
920
  }
1065
- // Get list of all logs
1066
921
  const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs?api-version=${this.apiVersion}`);
1067
922
  return {
1068
923
  buildId,
@@ -1078,917 +933,469 @@ export class AzureDevOpsService {
1078
933
  }))
1079
934
  };
1080
935
  }
1081
- /**
1082
- * Create a new pipeline definition
1083
- * @param project The project name
1084
- * @param name Pipeline name
1085
- * @param repositoryId Repository ID
1086
- * @param yamlPath Path to YAML file in repository
1087
- * @param folder Optional folder path
1088
- * @returns Created pipeline definition
1089
- */
1090
- async createPipelineDefinition(project, name, repositoryId, yamlPath, folder) {
1091
- this.validateProject(project);
1092
- if (!this.config.enablePipelineUpsert) {
1093
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1094
- }
1095
- const definition = {
1096
- name,
1097
- path: folder || '\\',
1098
- type: 'build',
1099
- queueStatus: 'enabled',
1100
- process: {
1101
- type: 2, // YAML process type
1102
- yamlFilename: yamlPath
1103
- },
1104
- repository: {
1105
- id: repositoryId,
1106
- type: 'TfsGit'
1107
- }
1108
- };
1109
- const response = await this.makeRequest(`${project}/_apis/build/definitions?api-version=${this.apiVersion}`, 'POST', definition);
1110
- return {
1111
- id: response.id,
1112
- name: response.name,
1113
- path: response.path,
1114
- revision: response.revision,
1115
- project,
1116
- url: response._links?.web?.href
1117
- };
1118
- }
1119
- /**
1120
- * Update a pipeline definition
1121
- * @param project The project name
1122
- * @param definitionId The pipeline definition ID
1123
- * @param updates Object with updated properties
1124
- * @returns Updated pipeline definition
1125
- */
1126
- async updatePipelineDefinition(project, definitionId, updates) {
1127
- this.validateProject(project);
1128
- if (!this.config.enablePipelineUpsert) {
1129
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1130
- }
1131
- // Get current definition first
1132
- const current = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`);
1133
- // Merge updates
1134
- const updated = {
1135
- ...current,
1136
- ...updates,
1137
- revision: current.revision // Must include current revision
1138
- };
1139
- const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`, 'PUT', updated);
1140
- return {
1141
- id: response.id,
1142
- name: response.name,
1143
- path: response.path,
1144
- revision: response.revision,
1145
- project,
1146
- url: response._links?.web?.href
1147
- };
1148
- }
1149
- /**
1150
- * Rename a pipeline definition
1151
- * @param project The project name
1152
- * @param definitionId The pipeline definition ID
1153
- * @param newName The new name
1154
- * @returns Updated pipeline definition
1155
- */
1156
- async renamePipelineDefinition(project, definitionId, newName) {
1157
- return this.updatePipelineDefinition(project, definitionId, { name: newName });
1158
- }
1159
- /**
1160
- * Queue a new build
1161
- * @param project The project name
1162
- * @param definitionId The pipeline definition ID
1163
- * @param branch Optional source branch
1164
- * @param variables Optional variables to pass
1165
- * @param parameters Optional pipeline parameters
1166
- * @returns Queued build
1167
- */
1168
- async queueBuild(project, definitionId, branch, variables, parameters) {
1169
- this.validateProject(project);
1170
- if (!this.config.enablePipelineUpsert) {
1171
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1172
- }
1173
- const buildRequest = {
1174
- definition: { id: definitionId }
1175
- };
1176
- if (branch) {
1177
- buildRequest.sourceBranch = branch;
1178
- }
1179
- if (variables) {
1180
- buildRequest.parameters = JSON.stringify(variables);
1181
- }
1182
- if (parameters) {
1183
- buildRequest.templateParameters = parameters;
1184
- }
1185
- const response = await this.makeRequest(`${project}/_apis/build/builds?api-version=${this.apiVersion}`, 'POST', buildRequest);
1186
- return {
1187
- id: response.id,
1188
- buildNumber: response.buildNumber,
1189
- status: response.status,
1190
- queueTime: response.queueTime,
1191
- definition: response.definition?.name,
1192
- sourceBranch: response.sourceBranch,
1193
- project,
1194
- url: response._links?.web?.href
1195
- };
1196
- }
1197
- /**
1198
- * Cancel a running build
1199
- * @param project The project name
1200
- * @param buildId The build ID
1201
- * @returns Cancelled build
1202
- */
1203
- async cancelBuild(project, buildId) {
1204
- this.validateProject(project);
1205
- if (!this.config.enablePipelineUpsert) {
1206
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1207
- }
1208
- const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`, 'PATCH', { status: 'cancelling' });
1209
- return {
1210
- id: response.id,
1211
- buildNumber: response.buildNumber,
1212
- status: response.status,
1213
- project,
1214
- message: 'Build cancellation requested'
1215
- };
1216
- }
1217
- /**
1218
- * Retry a failed build
1219
- * @param project The project name
1220
- * @param buildId The build ID to retry
1221
- * @returns New build
1222
- */
1223
- async retryBuild(project, buildId) {
1224
- this.validateProject(project);
1225
- if (!this.config.enablePipelineUpsert) {
1226
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1227
- }
1228
- // Get the original build to get definition and source info
1229
- const original = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`);
1230
- // Queue a new build with the same parameters
1231
- return this.queueBuild(project, original.definition.id, original.sourceBranch);
1232
- }
1233
- /**
1234
- * Delete a pipeline definition
1235
- * @param project The project name
1236
- * @param definitionId The pipeline definition ID
1237
- * @returns Deletion confirmation
1238
- */
1239
- async deletePipelineDefinition(project, definitionId) {
1240
- this.validateProject(project);
1241
- if (!this.config.enablePipelineDelete) {
1242
- throw new Error('Pipeline delete operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_DELETE=true to enable.');
1243
- }
1244
- await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`, 'DELETE');
1245
- return {
1246
- definitionId,
1247
- project,
1248
- deleted: true
1249
- };
1250
- }
1251
936
  // ═══════════════════════════════════════════════════════════════════════════════
1252
- // SERVICE CONNECTION OPERATIONS (DevOps Admin Tools)
937
+ // PULL REQUEST OPERATIONS
1253
938
  // ═══════════════════════════════════════════════════════════════════════════════
1254
939
  /**
1255
- * List all service connections in a project
940
+ * List all Git repositories in a project
1256
941
  * @param project The project name
1257
- * @returns List of service connections
942
+ * @returns List of repositories with their IDs
1258
943
  */
1259
- async listServiceConnections(project) {
944
+ async listRepositories(project) {
1260
945
  this.validateProject(project);
1261
- const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints?api-version=${this.apiVersion}`);
946
+ const response = await this.makeRequest(`${project}/_apis/git/repositories?api-version=${this.apiVersion}`);
1262
947
  return {
1263
948
  project,
1264
949
  totalCount: response.value.length,
1265
- serviceConnections: response.value.map((conn) => ({
1266
- id: conn.id,
1267
- name: conn.name,
1268
- type: conn.type,
1269
- url: conn.url,
1270
- description: conn.description,
1271
- isShared: conn.isShared,
1272
- isReady: conn.isReady,
1273
- owner: conn.owner,
1274
- createdBy: conn.createdBy?.displayName,
1275
- authorization: conn.authorization ? {
1276
- scheme: conn.authorization.scheme,
1277
- // Mask any sensitive parameters
1278
- parameters: conn.authorization.parameters ?
1279
- Object.keys(conn.authorization.parameters).reduce((acc, key) => {
1280
- acc[key] = key.toLowerCase().includes('password') ||
1281
- key.toLowerCase().includes('secret') ||
1282
- key.toLowerCase().includes('key') ||
1283
- key.toLowerCase().includes('token')
1284
- ? '***SECRET***'
1285
- : conn.authorization.parameters[key];
1286
- return acc;
1287
- }, {}) : {}
1288
- } : null
950
+ repositories: response.value.map((repo) => ({
951
+ id: repo.id,
952
+ name: repo.name,
953
+ url: repo.url,
954
+ defaultBranch: repo.defaultBranch,
955
+ size: repo.size,
956
+ remoteUrl: repo.remoteUrl,
957
+ webUrl: repo.webUrl
1289
958
  }))
1290
959
  };
1291
960
  }
1292
961
  /**
1293
- * Get a specific service connection
962
+ * List pull requests in a repository
1294
963
  * @param project The project name
1295
- * @param connectionId The service connection ID
1296
- * @returns Service connection details
1297
- */
1298
- async getServiceConnection(project, connectionId) {
964
+ * @param repositoryId Repository ID (GUID) or name
965
+ * @param status Filter by status: active, completed, abandoned, all (default: active)
966
+ * @param top Maximum results (default: 25)
967
+ * @param creatorId Filter by creator ID
968
+ * @param reviewerId Filter by reviewer ID
969
+ * @returns List of pull requests
970
+ */
971
+ async listPullRequests(project, repositoryId, status = 'active', top = 25, creatorId, reviewerId) {
1299
972
  this.validateProject(project);
1300
- const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`);
973
+ let url = `${project}/_apis/git/repositories/${repositoryId}/pullrequests?searchCriteria.status=${status}&$top=${top}&api-version=${this.apiVersion}`;
974
+ if (creatorId)
975
+ url += `&searchCriteria.creatorId=${creatorId}`;
976
+ if (reviewerId)
977
+ url += `&searchCriteria.reviewerId=${reviewerId}`;
978
+ const response = await this.makeRequest(url);
1301
979
  return {
1302
- id: response.id,
1303
- name: response.name,
1304
- type: response.type,
1305
- url: response.url,
1306
- description: response.description,
1307
- isShared: response.isShared,
1308
- isReady: response.isReady,
1309
- owner: response.owner,
1310
- createdBy: response.createdBy?.displayName,
1311
980
  project,
1312
- authorization: response.authorization ? {
1313
- scheme: response.authorization.scheme,
1314
- // Mask any sensitive parameters
1315
- parameters: response.authorization.parameters ?
1316
- Object.keys(response.authorization.parameters).reduce((acc, key) => {
1317
- acc[key] = key.toLowerCase().includes('password') ||
1318
- key.toLowerCase().includes('secret') ||
1319
- key.toLowerCase().includes('key') ||
1320
- key.toLowerCase().includes('token')
1321
- ? '***SECRET***'
1322
- : response.authorization.parameters[key];
1323
- return acc;
1324
- }, {}) : {}
1325
- } : null,
1326
- data: response.data || {},
1327
- serviceEndpointProjectReferences: response.serviceEndpointProjectReferences
1328
- };
1329
- }
1330
- /**
1331
- * Get available service connection types
1332
- * @returns List of service connection types
1333
- */
1334
- async getServiceConnectionTypes() {
1335
- const response = await this.makeRequest(`_apis/serviceendpoint/types?api-version=${this.apiVersion}`);
1336
- return {
981
+ repositoryId,
982
+ status,
1337
983
  totalCount: response.value.length,
1338
- types: response.value.map((type) => ({
1339
- name: type.name,
1340
- displayName: type.displayName,
1341
- description: type.description,
1342
- helpMarkDown: type.helpMarkDown,
1343
- authenticationSchemes: type.authenticationSchemes?.map((scheme) => ({
1344
- scheme: scheme.scheme,
1345
- displayName: scheme.displayName
1346
- })) || []
984
+ pullRequests: response.value.map((pr) => ({
985
+ pullRequestId: pr.pullRequestId,
986
+ title: pr.title,
987
+ description: pr.description ? this.truncate(pr.description, 200) : null,
988
+ status: pr.status,
989
+ createdBy: pr.createdBy?.displayName,
990
+ creationDate: pr.creationDate,
991
+ closedDate: pr.closedDate,
992
+ sourceBranch: pr.sourceRefName?.replace('refs/heads/', ''),
993
+ targetBranch: pr.targetRefName?.replace('refs/heads/', ''),
994
+ mergeStatus: pr.mergeStatus,
995
+ isDraft: pr.isDraft,
996
+ reviewerCount: pr.reviewers?.length || 0,
997
+ url: pr._links?.web?.href
1347
998
  }))
1348
999
  };
1349
1000
  }
1350
1001
  /**
1351
- * Create a new service connection
1002
+ * Get details of a specific pull request
1352
1003
  * @param project The project name
1353
- * @param name Connection name
1354
- * @param type Connection type
1355
- * @param configuration Type-specific configuration
1356
- * @returns Created service connection
1004
+ * @param repositoryId Repository ID (GUID) or name
1005
+ * @param pullRequestId The PR ID
1006
+ * @returns Pull request details with reviewers
1357
1007
  */
1358
- async createServiceConnection(project, name, type, configuration) {
1008
+ async getPullRequest(project, repositoryId, pullRequestId) {
1359
1009
  this.validateProject(project);
1360
- if (!this.config.enableServiceConnUpsert) {
1361
- throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
1362
- }
1363
- const endpoint = {
1364
- name,
1365
- type,
1366
- url: configuration.url || '',
1367
- description: configuration.description || '',
1368
- authorization: configuration.authorization,
1369
- data: configuration.data || {},
1370
- isShared: false,
1371
- isReady: true
1372
- };
1373
- const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints?api-version=${this.apiVersion}`, 'POST', endpoint);
1010
+ const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}?api-version=${this.apiVersion}`);
1374
1011
  return {
1375
- id: response.id,
1376
- name: response.name,
1377
- type: response.type,
1378
- isReady: response.isReady,
1379
- project
1380
- };
1381
- }
1382
- /**
1383
- * Update a service connection
1384
- * @param project The project name
1385
- * @param connectionId The service connection ID
1386
- * @param updates Updated properties (not credentials)
1387
- * @returns Updated service connection
1388
- */
1389
- async updateServiceConnection(project, connectionId, updates) {
1390
- this.validateProject(project);
1391
- if (!this.config.enableServiceConnUpsert) {
1392
- throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
1393
- }
1394
- // Get current connection first
1395
- const current = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`);
1396
- // Merge updates (but don't allow overwriting authorization for security)
1397
- const updated = {
1398
- ...current,
1399
- name: updates.name || current.name,
1400
- description: updates.description || current.description,
1401
- url: updates.url || current.url,
1402
- data: updates.data ? { ...current.data, ...updates.data } : current.data
1403
- };
1404
- const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'PUT', updated);
1405
- return {
1406
- id: response.id,
1407
- name: response.name,
1408
- type: response.type,
1409
- isReady: response.isReady,
1012
+ pullRequestId: response.pullRequestId,
1013
+ title: response.title,
1014
+ description: response.description,
1015
+ status: response.status,
1016
+ createdBy: {
1017
+ displayName: response.createdBy?.displayName,
1018
+ id: response.createdBy?.id,
1019
+ uniqueName: response.createdBy?.uniqueName
1020
+ },
1021
+ creationDate: response.creationDate,
1022
+ closedDate: response.closedDate,
1023
+ sourceBranch: response.sourceRefName?.replace('refs/heads/', ''),
1024
+ targetBranch: response.targetRefName?.replace('refs/heads/', ''),
1025
+ mergeStatus: response.mergeStatus,
1026
+ isDraft: response.isDraft,
1027
+ mergeId: response.lastMergeCommit?.commitId,
1028
+ sourceCommitId: response.lastMergeSourceCommit?.commitId,
1029
+ targetCommitId: response.lastMergeTargetCommit?.commitId,
1030
+ supportsIterations: response.supportsIterations,
1031
+ reviewers: (response.reviewers || []).map((r) => ({
1032
+ displayName: r.displayName,
1033
+ id: r.id,
1034
+ vote: r.vote,
1035
+ voteLabel: this.getVoteLabel(r.vote),
1036
+ isRequired: r.isRequired,
1037
+ hasDeclined: r.hasDeclined
1038
+ })),
1039
+ labels: response.labels?.map((l) => l.name) || [],
1040
+ autoComplete: response.autoCompleteSetBy ? {
1041
+ setBy: response.autoCompleteSetBy.displayName,
1042
+ mergeStrategy: response.completionOptions?.mergeStrategy
1043
+ } : null,
1044
+ url: response._links?.web?.href,
1410
1045
  project
1411
1046
  };
1412
1047
  }
1413
1048
  /**
1414
- * Share a service connection across projects
1415
- * @param connectionId The service connection ID
1416
- * @param projectIds Array of project IDs to share with
1417
- * @returns Updated service connection
1049
+ * Convert numeric vote to label
1418
1050
  */
1419
- async shareServiceConnection(connectionId, projectIds) {
1420
- if (!this.config.enableServiceConnUpsert) {
1421
- throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
1051
+ getVoteLabel(vote) {
1052
+ switch (vote) {
1053
+ case -10: return 'Rejected';
1054
+ case -5: return 'Waiting for author';
1055
+ case 0: return 'No response';
1056
+ case 5: return 'Approved with suggestions';
1057
+ case 10: return 'Approved';
1058
+ default: return `Unknown (${vote})`;
1422
1059
  }
1423
- // This requires fetching the current endpoint and updating its project references
1424
- // Note: This is a simplified implementation - full implementation would need project name lookup
1425
- const shareRequest = projectIds.map(projectId => ({
1426
- projectReference: { id: projectId },
1427
- name: '' // Will be set by API
1428
- }));
1429
- const response = await this.makeRequest(`_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'PATCH', { serviceEndpointProjectReferences: shareRequest });
1430
- return {
1431
- id: response.id,
1432
- name: response.name,
1433
- sharedWith: projectIds
1434
- };
1435
1060
  }
1436
1061
  /**
1437
- * Delete a service connection
1062
+ * Get commits in a pull request
1438
1063
  * @param project The project name
1439
- * @param connectionId The service connection ID
1440
- * @returns Deletion confirmation
1064
+ * @param repositoryId Repository ID (GUID) or name
1065
+ * @param pullRequestId The PR ID
1066
+ * @returns List of commits
1441
1067
  */
1442
- async deleteServiceConnection(project, connectionId) {
1068
+ async getPullRequestCommits(project, repositoryId, pullRequestId) {
1443
1069
  this.validateProject(project);
1444
- if (!this.config.enableServiceConnDelete) {
1445
- throw new Error('Service connection delete operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_DELETE=true to enable.');
1446
- }
1447
- await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'DELETE');
1070
+ const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/commits?api-version=${this.apiVersion}`);
1448
1071
  return {
1449
- connectionId,
1072
+ pullRequestId,
1450
1073
  project,
1451
- deleted: true
1452
- };
1453
- }
1454
- // ═══════════════════════════════════════════════════════════════════════════════
1455
- // VARIABLE GROUP UPSERT/DELETE OPERATIONS (DevOps Admin Tools)
1456
- // ═══════════════════════════════════════════════════════════════════════════════
1457
- /**
1458
- * Create a new variable group
1459
- * @param project The project name
1460
- * @param name Variable group name
1461
- * @param description Optional description
1462
- * @param variables Initial variables
1463
- * @returns Created variable group
1464
- */
1465
- async createVariableGroup(project, name, description, variables) {
1466
- this.validateProject(project);
1467
- if (!this.config.enableVariableGroupUpsert) {
1468
- throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
1469
- }
1470
- const variableGroup = {
1471
- name,
1472
- description: description || '',
1473
- type: 'Vsts',
1474
- variables: variables || {},
1475
- variableGroupProjectReferences: [{
1476
- projectReference: { name: project },
1477
- name: name
1478
- }]
1479
- };
1480
- const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups?api-version=${this.apiVersion}`, 'POST', variableGroup);
1481
- return {
1482
- id: response.id,
1483
- name: response.name,
1484
- project,
1485
- variableCount: Object.keys(response.variables || {}).length
1486
- };
1487
- }
1488
- /**
1489
- * Update a variable group's metadata
1490
- * @param project The project name
1491
- * @param groupId The variable group ID
1492
- * @param updates Updated properties
1493
- * @returns Updated variable group
1494
- */
1495
- async updateVariableGroupMetadata(project, groupId, updates) {
1496
- this.validateProject(project);
1497
- if (!this.config.enableVariableGroupUpsert) {
1498
- throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
1499
- }
1500
- // Get current group first
1501
- const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
1502
- const updated = {
1503
- ...current,
1504
- name: updates.name || current.name,
1505
- description: updates.description || current.description
1506
- };
1507
- const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', updated);
1508
- return {
1509
- id: response.id,
1510
- name: response.name,
1511
- description: response.description,
1512
- project
1074
+ totalCount: response.value.length,
1075
+ commits: response.value.map((c) => ({
1076
+ commitId: c.commitId,
1077
+ comment: c.comment,
1078
+ author: {
1079
+ name: c.author?.name,
1080
+ email: c.author?.email,
1081
+ date: c.author?.date
1082
+ },
1083
+ committer: {
1084
+ name: c.committer?.name,
1085
+ date: c.committer?.date
1086
+ },
1087
+ url: c.url
1088
+ }))
1513
1089
  };
1514
1090
  }
1515
1091
  /**
1516
- * Set or update a variable in a variable group
1092
+ * Get threads/comments on a pull request
1517
1093
  * @param project The project name
1518
- * @param groupId The variable group ID
1519
- * @param variableName Variable name
1520
- * @param value Variable value
1521
- * @param isSecret Whether the variable is secret
1522
- * @returns Updated variable group
1094
+ * @param repositoryId Repository ID (GUID) or name
1095
+ * @param pullRequestId The PR ID
1096
+ * @returns List of discussion threads
1523
1097
  */
1524
- async setVariable(project, groupId, variableName, value, isSecret = false) {
1098
+ async getPullRequestThreads(project, repositoryId, pullRequestId) {
1525
1099
  this.validateProject(project);
1526
- if (!this.config.enableVariableGroupUpsert) {
1527
- throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
1528
- }
1529
- // Get current group first
1530
- const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
1531
- // Update the variable
1532
- current.variables[variableName] = { value, isSecret };
1533
- const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', current);
1100
+ const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads?api-version=${this.apiVersion}`);
1534
1101
  return {
1535
- id: response.id,
1536
- name: response.name,
1102
+ pullRequestId,
1537
1103
  project,
1538
- variableSet: variableName,
1539
- isSecret
1104
+ totalCount: response.value.length,
1105
+ threads: response.value.map((t) => ({
1106
+ id: t.id,
1107
+ status: t.status,
1108
+ publishedDate: t.publishedDate,
1109
+ lastUpdatedDate: t.lastUpdatedDate,
1110
+ isDeleted: t.isDeleted,
1111
+ threadContext: t.threadContext ? {
1112
+ filePath: t.threadContext.filePath,
1113
+ rightFileStart: t.threadContext.rightFileStart,
1114
+ rightFileEnd: t.threadContext.rightFileEnd
1115
+ } : null,
1116
+ comments: (t.comments || []).filter((c) => !c.isDeleted).map((c) => ({
1117
+ id: c.id,
1118
+ author: c.author?.displayName,
1119
+ content: c.content,
1120
+ publishedDate: c.publishedDate,
1121
+ commentType: c.commentType,
1122
+ parentCommentId: c.parentCommentId
1123
+ }))
1124
+ }))
1540
1125
  };
1541
1126
  }
1542
1127
  /**
1543
- * Remove a variable from a variable group
1128
+ * Get file changes in a pull request
1544
1129
  * @param project The project name
1545
- * @param groupId The variable group ID
1546
- * @param variableName Variable name to remove
1547
- * @returns Updated variable group
1130
+ * @param repositoryId Repository ID (GUID) or name
1131
+ * @param pullRequestId The PR ID
1132
+ * @param iterationId Iteration ID (default: latest)
1133
+ * @returns List of changed files
1548
1134
  */
1549
- async removeVariable(project, groupId, variableName) {
1135
+ async getPullRequestChanges(project, repositoryId, pullRequestId, iterationId) {
1550
1136
  this.validateProject(project);
1551
- if (!this.config.enableVariableGroupDelete) {
1552
- throw new Error('Variable group delete operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_DELETE=true to enable.');
1553
- }
1554
- // Get current group first
1555
- const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
1556
- // Remove the variable
1557
- if (current.variables[variableName]) {
1558
- delete current.variables[variableName];
1559
- }
1560
- else {
1561
- throw new Error(`Variable '${variableName}' not found in group ${groupId}`);
1137
+ // If no iteration specified, get the latest
1138
+ let targetIteration = iterationId;
1139
+ if (!targetIteration) {
1140
+ const iterations = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/iterations?api-version=${this.apiVersion}`);
1141
+ if (iterations.value.length > 0) {
1142
+ targetIteration = iterations.value[iterations.value.length - 1].id;
1143
+ }
1144
+ else {
1145
+ throw new Error('No iterations found for this pull request');
1146
+ }
1562
1147
  }
1563
- const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', current);
1148
+ const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/iterations/${targetIteration}/changes?api-version=${this.apiVersion}`);
1564
1149
  return {
1565
- id: response.id,
1566
- name: response.name,
1150
+ pullRequestId,
1151
+ iterationId: targetIteration,
1567
1152
  project,
1568
- variableRemoved: variableName
1153
+ totalCount: response.changeEntries?.length || 0,
1154
+ changes: (response.changeEntries || []).map((c) => ({
1155
+ changeType: c.changeType,
1156
+ path: c.item?.path,
1157
+ originalPath: c.originalPath,
1158
+ objectId: c.item?.objectId,
1159
+ originalObjectId: c.item?.originalObjectId
1160
+ }))
1569
1161
  };
1570
1162
  }
1571
1163
  /**
1572
- * Delete a variable group
1164
+ * Add a comment thread to a pull request
1573
1165
  * @param project The project name
1574
- * @param groupId The variable group ID
1575
- * @returns Deletion confirmation
1576
- */
1577
- async deleteVariableGroup(project, groupId) {
1166
+ * @param repositoryId Repository ID (GUID) or name
1167
+ * @param pullRequestId The PR ID
1168
+ * @param content Comment content (markdown supported)
1169
+ * @param filePath Optional file path for inline comment
1170
+ * @param lineNumber Optional line number (right side) for inline comment
1171
+ * @param status Thread status: active, fixed, wontFix, closed, byDesign, pending (default: active)
1172
+ * @returns Created thread
1173
+ */
1174
+ async addPullRequestThread(project, repositoryId, pullRequestId, content, filePath, lineNumber, status = 'active') {
1578
1175
  this.validateProject(project);
1579
- if (!this.config.enableVariableGroupDelete) {
1580
- throw new Error('Variable group delete operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_DELETE=true to enable.');
1581
- }
1582
- await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'DELETE');
1583
- return {
1584
- groupId,
1585
- project,
1586
- deleted: true
1587
- };
1588
- }
1589
- // ═══════════════════════════════════════════════════════════════════════════════
1590
- // AGENT POOL OPERATIONS (DevOps Admin Tools)
1591
- // ═══════════════════════════════════════════════════════════════════════════════
1592
- /**
1593
- * List all agent pools
1594
- * @param poolType Optional filter: "automation" or "deployment"
1595
- * @returns List of agent pools
1596
- */
1597
- async listAgentPools(poolType) {
1598
- let url = `_apis/distributedtask/pools?api-version=${this.apiVersion}`;
1599
- if (poolType) {
1600
- url += `&poolType=${poolType}`;
1176
+ if (!this.config.enablePullRequestWrite) {
1177
+ throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
1178
+ }
1179
+ const threadData = {
1180
+ comments: [
1181
+ {
1182
+ parentCommentId: 0,
1183
+ content: content,
1184
+ commentType: 1 // 1 = text
1185
+ }
1186
+ ],
1187
+ status: status
1188
+ };
1189
+ // Add file context for inline comments
1190
+ if (filePath && lineNumber) {
1191
+ threadData.threadContext = {
1192
+ filePath: filePath.startsWith('/') ? filePath : `/${filePath}`,
1193
+ rightFileStart: { line: lineNumber, offset: 1 },
1194
+ rightFileEnd: { line: lineNumber, offset: 1 }
1195
+ };
1601
1196
  }
1602
- const response = await this.makeRequest(url);
1197
+ const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads?api-version=${this.apiVersion}`, 'POST', threadData);
1603
1198
  return {
1604
- totalCount: response.value.length,
1605
- pools: response.value.map((pool) => ({
1606
- id: pool.id,
1607
- name: pool.name,
1608
- size: pool.size,
1609
- isHosted: pool.isHosted,
1610
- poolType: pool.poolType,
1611
- createdBy: pool.createdBy?.displayName,
1612
- createdOn: pool.createdOn,
1613
- autoProvision: pool.autoProvision,
1614
- autoUpdate: pool.autoUpdate,
1615
- autoSize: pool.autoSize,
1616
- targetSize: pool.targetSize
1617
- }))
1618
- };
1619
- }
1620
- /**
1621
- * Get a specific agent pool
1622
- * @param poolId The pool ID
1623
- * @returns Agent pool details
1624
- */
1625
- async getAgentPool(poolId) {
1626
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}?api-version=${this.apiVersion}`);
1627
- return {
1628
- id: response.id,
1629
- name: response.name,
1630
- size: response.size,
1631
- isHosted: response.isHosted,
1632
- poolType: response.poolType,
1633
- createdBy: response.createdBy?.displayName,
1634
- createdOn: response.createdOn,
1635
- autoProvision: response.autoProvision,
1636
- autoUpdate: response.autoUpdate,
1637
- autoSize: response.autoSize,
1638
- targetSize: response.targetSize,
1639
- owner: response.owner?.displayName,
1640
- agentCloudId: response.agentCloudId,
1641
- properties: response.properties
1642
- };
1643
- }
1644
- /**
1645
- * List agents in a pool
1646
- * @param poolId The pool ID
1647
- * @param includeCapabilities Include agent capabilities
1648
- * @returns List of agents
1649
- */
1650
- async listAgents(poolId, includeCapabilities = false) {
1651
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents?includeCapabilities=${includeCapabilities}&api-version=${this.apiVersion}`);
1652
- return {
1653
- poolId,
1654
- totalCount: response.value.length,
1655
- agents: response.value.map((agent) => ({
1656
- id: agent.id,
1657
- name: agent.name,
1658
- version: agent.version,
1659
- osDescription: agent.osDescription,
1660
- enabled: agent.enabled,
1661
- status: agent.status,
1662
- provisioningState: agent.provisioningState,
1663
- createdOn: agent.createdOn,
1664
- maxParallelism: agent.maxParallelism,
1665
- systemCapabilities: includeCapabilities ? agent.systemCapabilities : undefined,
1666
- userCapabilities: includeCapabilities ? agent.userCapabilities : undefined
1667
- }))
1668
- };
1669
- }
1670
- /**
1671
- * Get a specific agent
1672
- * @param poolId The pool ID
1673
- * @param agentId The agent ID
1674
- * @returns Agent details
1675
- */
1676
- async getAgent(poolId, agentId) {
1677
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?includeCapabilities=true&api-version=${this.apiVersion}`);
1678
- return {
1679
- id: response.id,
1680
- name: response.name,
1681
- version: response.version,
1682
- osDescription: response.osDescription,
1683
- enabled: response.enabled,
1199
+ threadId: response.id,
1684
1200
  status: response.status,
1685
- provisioningState: response.provisioningState,
1686
- accessPoint: response.accessPoint,
1687
- createdOn: response.createdOn,
1688
- maxParallelism: response.maxParallelism,
1689
- systemCapabilities: response.systemCapabilities,
1690
- userCapabilities: response.userCapabilities,
1691
- assignedRequest: response.assignedRequest,
1692
- lastCompletedRequest: response.lastCompletedRequest
1693
- };
1694
- }
1695
- /**
1696
- * Update an agent pool
1697
- * @param poolId The pool ID
1698
- * @param updates Updated settings
1699
- * @returns Updated agent pool
1700
- */
1701
- async updateAgentPool(poolId, updates) {
1702
- if (!this.config.enableAgentPoolUpsert) {
1703
- throw new Error('Agent pool upsert operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_UPSERT=true to enable.');
1704
- }
1705
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}?api-version=${this.apiVersion}`, 'PATCH', updates);
1706
- return {
1707
- id: response.id,
1708
- name: response.name,
1709
- autoProvision: response.autoProvision,
1710
- autoUpdate: response.autoUpdate,
1711
- autoSize: response.autoSize,
1712
- targetSize: response.targetSize
1201
+ publishedDate: response.publishedDate,
1202
+ filePath: response.threadContext?.filePath,
1203
+ comments: response.comments?.map((c) => ({
1204
+ id: c.id,
1205
+ content: c.content,
1206
+ author: c.author?.displayName
1207
+ })),
1208
+ message: filePath
1209
+ ? `Inline comment added to ${filePath} at line ${lineNumber}`
1210
+ : 'Comment thread created',
1211
+ pullRequestId,
1212
+ project
1713
1213
  };
1714
1214
  }
1715
1215
  /**
1716
- * Enable a disabled agent
1717
- * @param poolId The pool ID
1718
- * @param agentId The agent ID
1719
- * @returns Updated agent
1216
+ * Create a new pull request
1720
1217
  */
1721
- async enableAgent(poolId, agentId) {
1722
- if (!this.config.enableAgentPoolUpsert) {
1723
- throw new Error('Agent pool upsert operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_UPSERT=true to enable.');
1218
+ async createPullRequest(project, repositoryId, sourceRefName, targetRefName, title, description, reviewerIds, isDraft) {
1219
+ this.validateProject(project);
1220
+ if (!this.config.enablePullRequestWrite) {
1221
+ throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
1724
1222
  }
1725
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?api-version=${this.apiVersion}`, 'PATCH', { enabled: true });
1726
- return {
1727
- id: response.id,
1728
- name: response.name,
1729
- enabled: response.enabled,
1730
- status: response.status
1223
+ const body = {
1224
+ sourceRefName,
1225
+ targetRefName,
1226
+ title,
1227
+ description: description || '',
1228
+ isDraft: isDraft || false,
1731
1229
  };
1732
- }
1733
- /**
1734
- * Disable an agent
1735
- * @param poolId The pool ID
1736
- * @param agentId The agent ID
1737
- * @returns Updated agent
1738
- */
1739
- async disableAgent(poolId, agentId) {
1740
- if (!this.config.enableAgentPoolDisable) {
1741
- throw new Error('Agent pool disable operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_DISABLE=true to enable.');
1230
+ if (reviewerIds && reviewerIds.length > 0) {
1231
+ body.reviewers = reviewerIds.map(id => ({ id }));
1742
1232
  }
1743
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?api-version=${this.apiVersion}`, 'PATCH', { enabled: false });
1233
+ const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests?api-version=${this.apiVersion}`, 'POST', body, false, { 'Content-Type': 'application/json' });
1744
1234
  return {
1745
- id: response.id,
1746
- name: response.name,
1747
- enabled: response.enabled,
1235
+ pullRequestId: response.pullRequestId,
1236
+ title: response.title,
1748
1237
  status: response.status,
1749
- message: 'Agent disabled - will complete current job then stop accepting new jobs'
1750
- };
1751
- }
1752
- // ═══════════════════════════════════════════════════════════════════════════════
1753
- // ENVIRONMENT OPERATIONS (DevOps Admin Tools)
1754
- // ═══════════════════════════════════════════════════════════════════════════════
1755
- /**
1756
- * List all environments in a project
1757
- * @param project The project name
1758
- * @returns List of environments
1759
- */
1760
- async listEnvironments(project) {
1761
- this.validateProject(project);
1762
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments?api-version=${this.apiVersion}`);
1763
- return {
1764
- project,
1765
- totalCount: response.value.length,
1766
- environments: response.value.map((env) => ({
1767
- id: env.id,
1768
- name: env.name,
1769
- description: env.description,
1770
- createdBy: env.createdBy?.displayName,
1771
- createdOn: env.createdOn,
1772
- lastModifiedBy: env.lastModifiedBy?.displayName,
1773
- lastModifiedOn: env.lastModifiedOn
1774
- }))
1775
- };
1776
- }
1777
- /**
1778
- * Get a specific environment
1779
- * @param project The project name
1780
- * @param environmentId The environment ID
1781
- * @returns Environment details
1782
- */
1783
- async getEnvironment(project, environmentId) {
1784
- this.validateProject(project);
1785
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?expands=resourceReferences&api-version=${this.apiVersion}`);
1786
- return {
1787
- id: response.id,
1788
- name: response.name,
1789
- description: response.description,
1238
+ isDraft: response.isDraft,
1239
+ sourceBranch: response.sourceRefName?.replace('refs/heads/', ''),
1240
+ targetBranch: response.targetRefName?.replace('refs/heads/', ''),
1790
1241
  createdBy: response.createdBy?.displayName,
1791
- createdOn: response.createdOn,
1792
- lastModifiedBy: response.lastModifiedBy?.displayName,
1793
- lastModifiedOn: response.lastModifiedOn,
1794
- project,
1795
- resources: response.resources || []
1796
- };
1797
- }
1798
- /**
1799
- * Get deployment history for an environment
1800
- * @param project The project name
1801
- * @param environmentId The environment ID
1802
- * @param top Maximum number of results
1803
- * @returns Deployment records
1804
- */
1805
- async getEnvironmentDeployments(project, environmentId, top = 10) {
1806
- this.validateProject(project);
1807
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}/environmentdeploymentrecords?top=${top}&api-version=${this.apiVersion}`);
1808
- return {
1809
- project,
1810
- environmentId,
1811
- totalCount: response.value.length,
1812
- deployments: response.value.map((dep) => ({
1813
- id: dep.id,
1814
- environmentId: dep.environmentId,
1815
- definition: dep.definition ? {
1816
- id: dep.definition.id,
1817
- name: dep.definition.name
1818
- } : null,
1819
- owner: dep.owner?.displayName,
1820
- planType: dep.planType,
1821
- startTime: dep.startTime,
1822
- finishTime: dep.finishTime,
1823
- result: dep.result,
1824
- queueTime: dep.queueTime
1825
- }))
1242
+ creationDate: response.creationDate,
1243
+ url: response.url,
1244
+ project
1826
1245
  };
1827
1246
  }
1828
1247
  /**
1829
- * Create a new environment
1830
- * @param project The project name
1831
- * @param name Environment name
1832
- * @param description Optional description
1833
- * @returns Created environment
1248
+ * Update an existing pull request (title, description, status, draft)
1834
1249
  */
1835
- async createEnvironment(project, name, description) {
1250
+ async updatePullRequest(project, repositoryId, pullRequestId, updates) {
1836
1251
  this.validateProject(project);
1837
- if (!this.config.enableEnvironmentUpsert) {
1838
- throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1839
- }
1840
- const environment = {
1841
- name,
1842
- description: description || ''
1843
- };
1844
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments?api-version=${this.apiVersion}`, 'POST', environment);
1252
+ if (!this.config.enablePullRequestWrite) {
1253
+ throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
1254
+ }
1255
+ const body = {};
1256
+ if (updates.title !== undefined)
1257
+ body.title = updates.title;
1258
+ if (updates.description !== undefined)
1259
+ body.description = updates.description;
1260
+ if (updates.status !== undefined)
1261
+ body.status = updates.status;
1262
+ if (updates.isDraft !== undefined)
1263
+ body.isDraft = updates.isDraft;
1264
+ const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}?api-version=${this.apiVersion}`, 'PATCH', body, false, { 'Content-Type': 'application/json' });
1845
1265
  return {
1846
- id: response.id,
1847
- name: response.name,
1848
- description: response.description,
1266
+ pullRequestId: response.pullRequestId,
1267
+ title: response.title,
1268
+ description: response.description ? this.truncate(response.description, 200) : null,
1269
+ status: response.status,
1270
+ isDraft: response.isDraft,
1849
1271
  project
1850
1272
  };
1851
1273
  }
1852
1274
  /**
1853
- * Update an environment
1854
- * @param project The project name
1855
- * @param environmentId The environment ID
1856
- * @param updates Updated properties
1857
- * @returns Updated environment
1275
+ * Complete (merge) a pull request
1858
1276
  */
1859
- async updateEnvironment(project, environmentId, updates) {
1277
+ async completePullRequest(project, repositoryId, pullRequestId, mergeStrategy = 'squash', deleteSourceBranch = true, transitionWorkItems = true, mergeCommitMessage) {
1860
1278
  this.validateProject(project);
1861
- if (!this.config.enableEnvironmentUpsert) {
1862
- throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1279
+ if (!this.config.enablePullRequestWrite) {
1280
+ throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
1281
+ }
1282
+ // Fetch current PR to get lastMergeSourceCommit
1283
+ const currentPr = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}?api-version=${this.apiVersion}`);
1284
+ const mergeStrategyMap = {
1285
+ noFastForward: 1,
1286
+ squash: 2,
1287
+ rebase: 3,
1288
+ rebaseMerge: 4,
1289
+ };
1290
+ const body = {
1291
+ status: 'completed',
1292
+ lastMergeSourceCommit: currentPr.lastMergeSourceCommit,
1293
+ completionOptions: {
1294
+ mergeStrategy: mergeStrategyMap[mergeStrategy],
1295
+ deleteSourceBranch,
1296
+ transitionWorkItems,
1297
+ },
1298
+ };
1299
+ if (mergeCommitMessage) {
1300
+ body.completionOptions.mergeCommitMessage = mergeCommitMessage;
1863
1301
  }
1864
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?api-version=${this.apiVersion}`, 'PATCH', updates);
1302
+ const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}?api-version=${this.apiVersion}`, 'PATCH', body, false, { 'Content-Type': 'application/json' });
1865
1303
  return {
1866
- id: response.id,
1867
- name: response.name,
1868
- description: response.description,
1304
+ pullRequestId: response.pullRequestId,
1305
+ title: response.title,
1306
+ status: response.status,
1307
+ mergeStrategy,
1308
+ closedDate: response.closedDate,
1869
1309
  project
1870
1310
  };
1871
1311
  }
1872
1312
  /**
1873
- * Delete an environment
1874
- * @param project The project name
1875
- * @param environmentId The environment ID
1876
- * @returns Deletion confirmation
1313
+ * Add or remove a reviewer from a pull request
1877
1314
  */
1878
- async deleteEnvironment(project, environmentId) {
1315
+ async addOrRemovePrReviewer(project, repositoryId, pullRequestId, reviewerId, isRequired, remove) {
1879
1316
  this.validateProject(project);
1880
- if (!this.config.enableEnvironmentDelete) {
1881
- throw new Error('Environment delete operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_DELETE=true to enable.');
1317
+ if (!this.config.enablePullRequestWrite) {
1318
+ throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
1882
1319
  }
1883
- await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?api-version=${this.apiVersion}`, 'DELETE');
1884
- return {
1885
- environmentId,
1886
- project,
1887
- deleted: true
1888
- };
1889
- }
1890
- /**
1891
- * Get all checks configured for an environment
1892
- * @param project The project name
1893
- * @param environmentId The environment ID
1894
- * @returns List of check configurations
1895
- */
1896
- async getEnvironmentChecks(project, environmentId) {
1897
- this.validateProject(project);
1898
- const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations?resourceType=environment&resourceId=${environmentId}&api-version=7.1-preview.1`);
1899
- return {
1900
- project,
1901
- environmentId,
1902
- totalCount: response.value.length,
1903
- checks: response.value.map((check) => ({
1904
- id: check.id,
1905
- type: check.type?.name || check.type?.id,
1906
- settings: check.settings,
1907
- timeout: check.timeout,
1908
- retryInterval: check.retryInterval,
1909
- createdBy: check.createdBy?.displayName,
1910
- createdOn: check.createdOn,
1911
- modifiedBy: check.modifiedBy?.displayName,
1912
- modifiedOn: check.modifiedOn,
1913
- resource: check.resource
1914
- }))
1915
- };
1916
- }
1917
- /**
1918
- * Add a check to an environment
1919
- * @param project The project name
1920
- * @param environmentId The environment ID
1921
- * @param checkType The type of check (e.g., "Approval", "ExclusiveLock")
1922
- * @param configuration Check configuration
1923
- * @returns Created check
1924
- */
1925
- async addEnvironmentCheck(project, environmentId, checkType, configuration) {
1926
- this.validateProject(project);
1927
- if (!this.config.enableEnvironmentUpsert) {
1928
- throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1320
+ const url = `${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/reviewers/${reviewerId}?api-version=${this.apiVersion}`;
1321
+ if (remove) {
1322
+ await this.makeRequest(url, 'DELETE');
1323
+ return { pullRequestId, reviewerId, action: 'removed', project };
1929
1324
  }
1930
- const check = {
1931
- type: { name: checkType },
1932
- settings: configuration,
1933
- resource: {
1934
- type: 'environment',
1935
- id: String(environmentId)
1936
- }
1937
- };
1938
- const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations?api-version=7.1-preview.1`, 'POST', check);
1325
+ const body = { id: reviewerId };
1326
+ if (isRequired !== undefined) {
1327
+ body.isRequired = isRequired;
1328
+ }
1329
+ const response = await this.makeRequest(url, 'PUT', body, false, { 'Content-Type': 'application/json' });
1939
1330
  return {
1940
- id: response.id,
1941
- type: response.type?.name,
1942
- environmentId,
1331
+ pullRequestId,
1332
+ reviewerId: response.id,
1333
+ displayName: response.displayName,
1334
+ isRequired: response.isRequired,
1335
+ vote: response.vote,
1336
+ action: 'added',
1943
1337
  project
1944
1338
  };
1945
1339
  }
1946
1340
  /**
1947
- * Update an existing environment check
1948
- * @param project The project name
1949
- * @param checkId The check configuration ID
1950
- * @param updates Updated settings and/or timeout
1951
- * @returns Updated check
1341
+ * Submit a vote on a pull request
1952
1342
  */
1953
- async updateEnvironmentCheck(project, checkId, updates) {
1343
+ async votePullRequest(project, repositoryId, pullRequestId, vote, reviewerId) {
1954
1344
  this.validateProject(project);
1955
- if (!this.config.enableEnvironmentUpsert) {
1956
- throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1345
+ if (!this.config.enablePullRequestWrite) {
1346
+ throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
1347
+ }
1348
+ const voteMap = {
1349
+ approve: 10,
1350
+ approveWithSuggestions: 5,
1351
+ noResponse: 0,
1352
+ waitForAuthor: -5,
1353
+ reject: -10,
1354
+ };
1355
+ // If no reviewerId, resolve authenticated user
1356
+ let userId = reviewerId;
1357
+ if (!userId) {
1358
+ const connectionData = await this.makeRequest('_apis/connectionData');
1359
+ userId = connectionData.authenticatedUser?.id;
1360
+ if (!userId) {
1361
+ throw new Error('Could not resolve authenticated user ID. Provide reviewerId explicitly.');
1362
+ }
1957
1363
  }
1958
- // Get current check configuration first
1959
- const current = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`);
1960
- // Merge updates
1961
- const updated = {
1962
- ...current,
1963
- settings: updates.settings !== undefined ? updates.settings : current.settings,
1964
- timeout: updates.timeout !== undefined ? updates.timeout : current.timeout
1965
- };
1966
- const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`, 'PATCH', updated);
1364
+ const body = { vote: voteMap[vote] };
1365
+ const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/reviewers/${userId}?api-version=${this.apiVersion}`, 'PUT', body, false, { 'Content-Type': 'application/json' });
1967
1366
  return {
1968
- id: response.id,
1969
- type: response.type?.name,
1970
- settings: response.settings,
1971
- timeout: response.timeout,
1367
+ pullRequestId,
1368
+ reviewerId: response.id,
1369
+ displayName: response.displayName,
1370
+ vote: response.vote,
1371
+ voteLabel: vote,
1972
1372
  project
1973
1373
  };
1974
1374
  }
1975
1375
  /**
1976
- * Remove a check from an environment
1977
- * @param project The project name
1978
- * @param checkId The check configuration ID
1979
- * @returns Deletion confirmation
1376
+ * Reply to a PR thread and optionally update thread status
1980
1377
  */
1981
- async removeEnvironmentCheck(project, checkId) {
1378
+ async replyToPrThread(project, repositoryId, pullRequestId, threadId, content, status) {
1982
1379
  this.validateProject(project);
1983
- if (!this.config.enableEnvironmentDelete) {
1984
- throw new Error('Environment delete operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_DELETE=true to enable.');
1380
+ if (!this.config.enablePullRequestWrite) {
1381
+ throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
1382
+ }
1383
+ const results = { pullRequestId, threadId, project };
1384
+ // Add reply comment if content provided
1385
+ if (content) {
1386
+ const commentResponse = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads/${threadId}/comments?api-version=${this.apiVersion}`, 'POST', { content, parentCommentId: 0, commentType: 1 }, false, { 'Content-Type': 'application/json' });
1387
+ results.comment = {
1388
+ id: commentResponse.id,
1389
+ content: commentResponse.content,
1390
+ author: commentResponse.author?.displayName,
1391
+ };
1985
1392
  }
1986
- await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`, 'DELETE');
1987
- return {
1988
- checkId,
1989
- project,
1990
- deleted: true
1991
- };
1393
+ // Update thread status if provided
1394
+ if (status) {
1395
+ await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads/${threadId}?api-version=${this.apiVersion}`, 'PATCH', { status }, false, { 'Content-Type': 'application/json' });
1396
+ results.status = status;
1397
+ }
1398
+ return results;
1992
1399
  }
1993
1400
  }
1994
1401
  //# sourceMappingURL=AzureDevOpsService.js.map